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

2

6

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)
Neoteric answered 8/2, 2012 at 14:51 Comment(0)
D
1

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

Disenthrone answered 8/2, 2012 at 16:21 Comment(2)
Thanks for your answer. But why would gSOAP touch standard input stream at all here? It reads data from a file stream (soap->is->read() is executed), not from a stdio. gSOAP leaves stdio in the binary mode after soap_begin_send() and never reverts its back to the default, text mode.Neoteric
You do not need to modify recvfd to use the streams. The soap->is stream takes priority over soap->recvfd.Disenthrone
L
1

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

Lex answered 9/2, 2012 at 15:4 Comment(2)
Thank you for your post but it doesn't give the answer. We should not be modifying recvfd if we're reading from std::istream (soap->is). I posted this question to gSOAP mailing list and now am waiting someone to confirm whether this is a bug or not.Neoteric
my program crashes when set soap->recvfd = -1; and otherwise it hangs as you have already mentioned. what to do?Bespatter

© 2022 - 2024 — McMap. All rights reserved.