Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does gSOAP set stdin mode to binary if reads data from a file stream?

I've been playing with gSOAP XML data binding by loading XML document into C++ class, modifying data and serializing it back into XML.

Here's the snippet of the XML - library.xml:

<?xml version="1.0" encoding="UTF-8"?>    
<gt:Library xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gt="http://www.bk.com/gSOAP/test">
    <gt:Books>
        <gt:Book isbn="0132350882" author="Robert C. Martin" title="Clean Code">
            <gt:CopiesAvailable>2</gt:CopiesAvailable>
        </gt:Book>
        <gt:Book isbn="020161622X" author="Andrew Hunt" title="The Pragmatic Programmer">
            <gt:CopiesAvailable>0</gt:CopiesAvailable>
        </gt:Book>
        <gt:Book isbn="0201633612" author="Erich Gamma" title="Design patterns">
            <gt:CopiesAvailable>1</gt:CopiesAvailable>
        </gt:Book>      
    </gt:Books>
    ...
</gt:Library>

The following code loads XML into object, modifies object and serializes it back into XML. Note that XML is loaded from a file, via file stream and that data to be added is obtained from a user, via stdin (cin).

main.cpp:

#include "soapH.h"
#include "gt.nsmap"
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using std::cin;
using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::fstream;
using std::string;
using std::stringstream;

void DisplayAllBooks(const _gt__Library& library)
{
    cout << "\n\nDisplaying all books in the library:" << endl;

    std::vector<_gt__Library_Books_Book>::const_iterator it = library.Books.Book.begin();
    for(;it != library.Books.Book.end(); it++)
    {
        cout << "\nBook:\n" << "\tTitle:" << (*it).title << "\n\tAuthor:" << (*it).author <<"\n\tISBN: " << (*it).isbn << "\n\tCopies available: " << static_cast<int>((*it).CopiesAvailable) << endl;
    }
}

void AddBook(_gt__Library& library)
{
    cout << "\n\nAdding a new book:" << endl;

    _gt__Library_Books_Book book;

    cout << "\tTitle: " << std::flush;
    getline(cin, book.title);

    cout << "\tAuthor: " << std::flush;
    getline(cin, book.author);

    cout << "\tISBN:" << std::flush;
    getline(cin, book.isbn);

    cout << "\tCopies available: " << std::flush;
    string strCopiesAvailable;
    getline(cin, strCopiesAvailable);
    stringstream ss(strCopiesAvailable);
    ss >> book.CopiesAvailable;

    library.Books.Book.push_back(book);
}

// Terminate and destroy soap
void DestroySoap(struct soap* pSoap)
{
    // remove deserialized class instances (C++ objects) 
    soap_destroy(pSoap); 

    // clean up and remove deserialized data 
    soap_end(pSoap);  

    // detach context (last use and no longer in scope)
    soap_done(pSoap);
}

int main()
{

    //
    // Create and intialize soap
    //

    // gSOAP runtime context
    struct soap soap; 

    // initialize runtime context 
    soap_init(&soap); 

    // Set input mode
    soap_imode(&soap, SOAP_ENC_XML);

    // reset deserializers; start new (de)serialization phase 
    soap_begin(&soap); 

    //
    // Load XML (Deserialize)
    //

    _gt__Library library;   
    string strXML = "library.xml";

    ifstream fstreamIN(strXML);
    soap.is = &fstreamIN;                               

    // calls soap_begin_recv, soap_get__gt__Library and soap_end_recv
    if(soap_read__gt__Library(&soap, &library) != SOAP_OK)
    {
        std::cout << "soap_read__gt__Library() failed" << std::endl;
        DestroySoap(&soap);
        return 1;
    }

    fstreamIN.close();

    //
    // Display books before and after adding a new book
    //

    DisplayAllBooks(library);
    AddBook(library);
    DisplayAllBooks(library);

    //
    // Serialize
    //

    soap_set_omode(&soap, SOAP_XML_INDENT); 

    ofstream fstreamOUT("library.xml");
    soap.os = &fstreamOUT;

    // calls soap_begin_send, soap_serialize, soap_put and soap_end_send
    if(soap_write__gt__Library(&soap, &library) != SOAP_OK) 
    {
        std::cout << "soap_write__gt__Library() failed" << std::endl;
        DestroySoap(&soap);
        return 1;
    }

    fstreamOUT.close();

    DestroySoap(&soap);

    return 0;
}

After running this test application, everything is fine apart from all newly added elements have strings that terminate with a Carriage Return character (CR - &#xD;):

Modified XML looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<gt:Library xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gt="http://www.bk.com/gSOAP/test">
    <gt:Books>
        <gt:Book isbn="0132350882" author="Robert C. Martin" title="Clean Code">
            <gt:CopiesAvailable>2</gt:CopiesAvailable>
        </gt:Book>
        <gt:Book isbn="020161622X" author="Andrew Hunt" title="The Pragmatic Programmer">
            <gt:CopiesAvailable>0</gt:CopiesAvailable>
        </gt:Book>
        <gt:Book isbn="0201633612" author="Erich Gamma" title="Design patterns">
            <gt:CopiesAvailable>1</gt:CopiesAvailable>
        </gt:Book>
        <gt:Book isbn="12345678&#xD;" author="Scott Meyers&#xD;" title="Effective C++&#xD;">
            <gt:CopiesAvailable>123</gt:CopiesAvailable>
        </gt:Book>
    </gt:Books>
    ...
</gt:Library>

I traced the source of the bug and found the following:

soap_read__gt__Library() calls soap_begin_send() which executes the following line:

_setmode(soap->recvfd, _O_BINARY);

soap->recvfd is set to 0 in soap_init() and 0 is a value of file descriptor of stdin.

Once stdin's mode is changed to a binary, STL library does not parse \r\n to a single \n for read operations and getline(cin, str), as usual, reads everything up to \n, copying \r into output string. And that is exactly Carriage Return character which appears in new strings in the final XML.

My question is: Why does gSOAP modify stdin mode if the data source is a file stream? Is this a bug in gSOAP?

NOTE:

As expected, if stdio's mode is reverted back to _O_TEXT after soap_begin_send() but before reading data from std::cin, getline() works fine. Here is the patch:

_setmode(_fileno(stdin), _O_TEXT)
like image 249
Bojan Komazec Avatar asked Feb 08 '12 14:02

Bojan Komazec


2 Answers

Also this cause unwanted side effects! By using

_setmode(soap->recvfd, _O_BINARY); // found in stdsoap2.cpp

you always assume to read from a file or stdin, however if you set soap->is to a std::istringstream this isn't true.

Just assume you are using getchar() in your main routine, but on a thread you try to deserialize a xml from a std:istringstream (using gsoap xml binding). As result your program will hang. the only way around is to set soap->recvfd = -1;

Regards Ralph

like image 26
Ralph Avatar answered Nov 10 '22 12:11

Ralph


This is to avoid encoding problems when reading and writing Unicode and UTF-8 encoded XML.

like image 121
Dr. Alex RE Avatar answered Nov 10 '22 12:11

Dr. Alex RE