t3ntman
t3ntman

Reputation: 163

HttpSendRequest - POST data not supporting Unicode

I'm working on making a C++ agent that will post information (such as the system hostname) back to a central server using HttpSendRequest(). One of the pieces of information that I want it to post back is the OS. I created the following function that will obtain the system hostname.

wstring getOS()
{
     HKEY key;
     RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, KEY_QUERY_VALUE, &key); // Obtains Registry handle

     DWORD type;
     wchar_t buffer[MAX_PATH]; // MAX_PATH = 260 - The system hostname should never exceed this value
     DWORD size = sizeof(buffer);
     RegQueryValueEx(key, L"ProductName", NULL, &type, (LPBYTE)&buffer, &size); // Queries Registry key - stores value in "buffer"
     wstring os(buffer); // Converts from C-style character array to wstring
     return os; // Returns wstring to caller
}

This function will obtain the OS using the Registry and store it as a wstring. I then want to pass the returned "os" wstring to the following post() function, but I noticed that you must use a string instead of a wstring for the HTTP POST data. Below is the code for my post() function:

void post()
{
     HINTERNET hInternetOpen = InternetOpen(userAgent.c_str(), INTERNET_OPEN_TYPE_PROXY, L"http://127.0.0.1:9999", NULL, 0);
     HINTERNET hInternetConnect = InternetConnect(hInternetOpen, host.c_str(), INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
     HINTERNET hHttpOpenRequest = HttpOpenRequest(hInternetConnect, L"POST", file.c_str(), NULL, NULL, NULL, 0, 0);

     wstring headers = L"Content-Type: application/x-www-form-urlencoded"; // Content-Type is necessary to POST

     string postData = "os="; // Why does this have to be a string and not a wstring?

     HttpSendRequest(hHttpOpenRequest, headers.c_str(), headers.length(), (LPVOID)postData.c_str(), postData.size());

     InternetCloseHandle(hInternetOpen);
     InternetCloseHandle(hInternetConnect);
     InternetCloseHandle(hHttpOpenRequest);
}

If I try to make "postData" a wstring, I get something that looks like the image below:

HTTP POST data as wstring

Can someone shed some light onto the easiest way to include a wstring as the POST data?

Upvotes: 2

Views: 3598

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595557

HttpSendRequest() only knows about raw bytes, not strings. You can send UTF-16 data using a std::wstring, but you have to tell the server that you are sending UTF-16, via a charset attribute in the Content-Type header.

wstring headers = L"Content-Type: application/x-www-form-urlencoded; charset=utf-16";

// TODO: don't forget to URL-encode the value from getOS() to
// escape reserved characters, including '=' and '&'...
wstring postData = L"os=" + getOS();

HttpSendRequest(hHttpOpenRequest, headers.c_str(), headers.length(),
    postData.c_str(), postData.length() * sizeof(wchar_t));

Note the use of sizeof(wchar_t) above. In your screenshot, your sniffer is showing the raw data, and the data it shows is what UTF-16 would look like, but you see only half of your wstring data because you are setting the dwOptionalLength parameter of HttpSendRequest() to a character count (7) instead of a byte count (14):

dwOptionalLength [in]
The size of the optional data, in bytes. This parameter can be zero if there is no optional data to send.

When you use std::string, the character count and the byte count are the same value.

What you really should be sending is UTF-8 instead of UTF-16, eg:

string Utf8Encode(const wstring &wstr)
{
    // NOTE: C++11 has built-in support for converting between
    // UTF-8 and UTF-16.  See the std::wstring_convert class...
    /*
    wstring_convert<codecvt_utf8_utf16<wchar_t>> conv;
    return conv.to_bytes(wstr);
    */

    string out;
    int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.length(), NULL, 0, NULL, NULL);
    if (len > 0)
    {
        out.resize(len);
        WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.length(), &out[0], len, NULL, NULL);
    }
    return out;
}

wstring headers = L"Content-Type: application/x-www-form-urlencoded; charset=utf-8";

// TODO: don't forget to URL-encode the value from getOS() to
// escape reserved characters, including '=' and '&'...
string postData = "os=" + Utf8Encode(getOS());

HttpSendRequest(hHttpOpenRequest, headers.c_str(), headers.length(),
    postData.c_str(), postData.size());

Upvotes: 6

Related Questions