Sending a POST using HttpSendRequestA fails with ERROR_HTTP_HEADER_NOT_FOUND? - c++

I'm trying to post data to a test .php script using XAMPP on my local machine, but it always fails with ERROR_HTTP_HEADER_NOT_FOUND. I check the server logs and no request was sent to Apache. I can manually do it via a web browser and the php script runs as expected.
Here's the C++ code that doesn't work. Can someone point out the problem?
static const char *accepttypes[]={ "application/x-www-form-urlencoded", NULL};
static const char headers[]="application/x-www-form-urlencoded\r\n";
// open session
HINTERNET hsession;
if ((hsession=InternetOpen(_T("Whatever"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0)) != NULL) {
HINTERNET hconnect;
if ((hconnect=InternetConnect(hsession, _T("127.0.0.1"), INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0)) != NULL) {
HINTERNET hpostreq;
if ((hpostreq=HttpOpenRequestA(hconnect, "POST", "/mytest.php", NULL, NULL, accepttypes, INTERNET_FLAG_RELOAD, 0)) != NULL) {
BYTE *data=(BYTE*)"xyz=1234";
DWORD datasize=8;
if (HttpSendRequestA(hpostreq, headers, sizeof(headers), (LPVOID) data, datasize)) {
....
// ^^^^^ That always fails with ERROR_HTTP_HEADER_NOT_FOUND

I see two mistakes in your call to HttpSendRequestA():
The value of headers needs to be changed from "application/x-www-form-urlencoded\r\n" to "Content-Type: application/x-www-form-urlencoded".
sizeof(headers) needs to be changed to either -1, sizeof(headers)-1, or strlen(headers). You are expected to pass in either -1 to let the function calculate the character length of the headers string, or you can pass in the actual character length. The string's null terminator is not to be included in that length.

Related

Bad Request: message text is empty when sending get request via winapi to telegram bot

I'm trying to send message to telegram chat from bot using winapi and c++.
Here is my code
char szData[1024];
// initialize WinInet
HINTERNET hInternet = ::InternetOpen(TEXT("WinInet Test"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInternet != NULL)
{
// open HTTP session
HINTERNET hConnect = ::InternetConnect(hInternet, L"api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, 1);
if (hConnect != NULL)
{
wstring request = L"/bot<bot_id>/sendMessage";
// open request
HINTERNET hRequest = ::HttpOpenRequest(hConnect, L"GET", (LPCWSTR)request.c_str(), NULL, NULL, 0, INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE, 1);
if (hRequest != NULL)
{
// send request
const wchar_t* params = L"?chat_id=<chat_id>&text=test";
BOOL isSend = ::HttpSendRequest(hRequest, NULL, 0, (LPVOID)params, wcslen(params));
if (isSend)
{
for (;;)
{
// reading data
DWORD dwByteRead;
BOOL isRead = ::InternetReadFile(hRequest, szData, sizeof(szData) - 1, &dwByteRead);
// break cycle if error or end
if (isRead == FALSE || dwByteRead == 0)
break;
// saving result
szData[dwByteRead] = 0;
}
}
// close request
::InternetCloseHandle(hRequest);
}
// close session
::InternetCloseHandle(hConnect);
}
// close WinInet
::InternetCloseHandle(hInternet);
}
wstring answer = CharPToWstring(szData);
return answer;
But I've got {"ok":false,"error_code":400,"description":"Bad Request: message text is empty"} response. <chat_id> is id consisted of digits(12345678).
If I run this request in postman or in browser - then everything is ok.
I also tried to run this request using WinHttp* methods and result is the same.
What should I change in my request parameters to make it work?
There are a number of issues with this code:
You don't need to typecast the return value of wstring::c_str() to LPCWSTR (aka const wchar_t*), as it is already that type.
You can't send body data in a GET request. The Telegram Bot API expects body data to be sent in a POST request instead.
You are telling HttpSendRequest() to send body data from a wchar_t* UTF-16 string, but that is not the correct encoding that the server is expecting. You need to use a char* UTF-8 string instead.
You are not sending a Content-Type request header to tell the server what the format of the body data is. The API supports several different formats. In this case, since you are sending the data in application/x-www-form-urlencoded format, you need to add a Content-Type: application/x-www-form-urlencoded header to the request.
With all of that said, try this instead:
// initialize WinInet
HINTERNET hInternet = ::InternetOpenW(L"WinInet Test", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hInternet == NULL) ... // error handling
// open HTTP session
HINTERNET hConnect = ::InternetConnectW(hInternet, L"api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, NULL, 1);
if (hConnect == NULL) ... // error handling
// open request
wstring wsResource = L"/bot<bot_id>/sendMessage";
HINTERNET hRequest = ::HttpOpenRequestW(hConnect, L"POST", wsResource.c_str(), NULL, NULL, 0, INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_SECURE, 1);
if (hRequest == NULL) ... // error handling
// send request
string sBody = u8"chat_id=<chat_id>&text=test";
BOOL isSend = ::HttpSendRequestW(hRequest, L"Content-Type: application/x-www-form-urlencoded", -1L, sBody.c_str(), sBody.size());
if (!isSend) ... // error handling
string sReply;
char szData[1024];
DWORD dwByteRead;
while (::InternetReadFile(hRequest, szData, sizeof(szData), &dwByteRead) && dwByteRead != 0)
{
// saving result
sReply.append(szData, dwByteRead);
}
...
// use sReply as needed ...

POST request trucated with wstring type and well formed using string

I want to make a POST request using Wininet with UTF-8 special char like €.
void sendArticle()
{
LPCWSTR browser = L"MyClientApp/1.0";
LPCWSTR domain = L"127.0.0.1";
LPCWSTR methode = L"POST";
LPCWSTR page = L"/shopping/article.php";
std::wstring strContentType = L"Content-Type: application/x-www-form-urlencoded; charset=utf-8";
LPCWSTR contentType = strContentType.c_str();
std::wstring dataStr = L"article=thecontent";
LPVOID data = (LPVOID)dataStr.c_str();
HINTERNET hInternet = InternetOpenW(browser, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
HINTERNET hConnection = InternetConnectW(hInternet, domain, 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
HINTERNET hRequest = HttpOpenRequestW(hConnection, methode, page, NULL, NULL, NULL, 0, 1);
HttpSendRequestW(hRequest, contentType, strContentType.size(), data, dataStr.size());
}
If I use string type for variable dataStr, the content is sent correctly. If I use wstring type for the variable (to send UTF-8 content), the request is truncated.
I can observe TCP packet using Wireshark.
To resume, this line std::string dataStr = "article=thecontent"; works but this line std::wstring dataStr = L"article=thecontent"; not works.
I checked the int returned by size() method and it's correct. What is this strange phenomenon and how to solve it ?
HttpSendRequestW takes a size in bytes for the data size parameter but you are using wide strings so you really want:
HttpSendRequestW(hRequest, contentType, strContentType.size(), data, sizeof(WCHAR)*dataStr.size());
However, http really works properly using utf8 so sending utf16 is kinda weird (although as you can interpret the data-payload however you wish so sending utf16 is ok I guess).
What you really want to do is just send utf8 anyway - which you can do using narrow strings - just make dataStr a std::string and fill it with utf8 code points and you're good.
The server expects UTF8 data (dataStr), so data must be converted from UTF16 to UTF8. Keep everything else in wide char format, as those functions are properly handled by wide string APIs
//convert dataStr to UTF8
int sz = WideCharToMultiByte(CP_UTF8, 0, dataStr.c_str(), -1, 0, 0, 0, 0);
std::string utf8(sz, 0);
WideCharToMultiByte(CP_UTF8, 0, dataStr.c_str(), (int)dataStr.size(), &utf8[0], sz, 0, 0);
//send UTF8 data
HttpSendRequestW(hRequest, contentType, strContentType.size(), utf8.data(), utf8.size());
I had the same problem and this is the solution I found and it works perfectlly :
void sendHello()
{
LPCSTR header = "Content-Type: application/x-www-form-urlencoded; charset=utf-8";
std::string dataStr = u8"message=ééàà€€";
LPVOID myMessage = (LPVOID)dataStr.c_str();
HINTERNET hInternet = InternetOpenA("InetURL/1.0", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
HINTERNET hConnection = InternetConnectA(hInternet, "127.0.0.1", 80, " ", " ", INTERNET_SERVICE_HTTP, 0, 1);
HINTERNET hRequest = HttpOpenRequestA(hConnection, "POST", "/SendMessage.php", NULL, NULL, NULL, 0, 1);
HttpSendRequestA(hRequest, header, strlen(header), myMessage, dataStr.size());
}

Adding progressbar to wininet http upload C++

I'm having code for uploading binary files on HTTP-server with POST method:
http_upload_file(PCHAR szServer, PCHAR szScript, PCHAR szParam, PCHAR szValue, PCHAR szFile)
{
PCHAR szHeaders = "Content-Type: multipart/form-data; boundary=----qwerty";
PCHAR szData = "------qwerty\r\n"
"Content-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n"
"------qwerty\r\n"
"Content-Disposition: form-data; name=\"files[]\"; filename=\"%s\"\r\n"
"Content-Type: application/octet-stream\r\n"
"Content-Transfer-Encoding: binary\r\n\r\n";
PCHAR szDataEnd = "\r\n------qwerty--\r\n";
char szHeader[512];
HINTERNET hSession, hConnect, hRequest;
DWORD dwFileSize, dwBytesRead, dwContentLength,dwBytesWritten;
hSession = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (hSession)
{
hConnect = InternetConnect(hSession, szServer, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP,0, 0);
if (hConnect)
{
hRequest = HttpOpenRequest(hConnect, "POST", szScript, NULL, NULL, 0, 0, 0);
if (hRequest)
{
HANDLE hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
dwFileSize = GetFileSize(hFile, NULL);
wsprintf(szHeader, szData, szParam, szValue, szFile);
dwContentLength = lstrlen(szHeader) + dwFileSize + lstrlen(szDataEnd);
LPBYTE pBuf = (LPBYTE)malloc(dwContentLength);
CopyMemory(&pBuf[0], szHeader, lstrlen(szHeader));
ReadFile(hFile, &pBuf[lstrlen(szHeader)], dwFileSize, &dwBytesRead, NULL);
CopyMemory(&pBuf[lstrlen(szHeader) + dwFileSize], szDataEnd, lstrlen(szDataEnd));
HttpSendRequest(hRequest, szHeaders, lstrlen(szHeaders), pBuf, dwContentLength);
CloseHandle(hFile);
free(pBuf);
}
}
InternetCloseHandle(hRequest);
}
InternetCloseHandle(hConnect);
}
InternetCloseHandle(hSession);
}
It's working fine, but I would like to add some proggess information while file is uploading.
Can I get size of transfered content, while HttpSendRequest(hRequest, szHeaders, lstrlen(szHeaders), pBuf, dwContentLength); is executing? The problem is that when uploading big file my Form is freezing and user can't watch how much data already uploaded. Because of this I want to add ProggessBar for displaying size of data transfering, but dont know how get this transfering data...
I will be glad for any advice.
You can split the upload into small pieces and update the progress bar after sending each piece.
Call InternetConnect() and HttpOpenRequest() as usual.
Instead of HttpSendRequest() call HttpSendRequestEx(). Define the total file size via the lpBuffersIn parameter. For parameter dwFlags pass the value HSR_INITIATE to tell the API that you want iterative data transfer. There is a doc bug in the reference page, the dwFlags parameter is not reserved. Everyone is using it, for example MFC.
INTERNET_BUFFERS buffer{ sizeof(buffer) };
buffer.dwBufferTotal = totalFileSizeInBytes;
BOOL success = HttpSendRequestEx( hRequest, &buffer, nullptr, HSR_INITIATE, 0 );
Call InternetWriteFile() in a loop to upload the file in pieces. Use a buffer size that is not too small to reduce overhead of API (say 16k). After each call to this API you could measure difference between current time and time of last progress bar update and only update progress bar if difference is big enough (say 1/10 s) to reduce API overhead of updating GUI.
When you are finished uploading the file, tell it the API by calling HttpEndRequest(). Of course close handles, check for errors, etc. pp.
All of this should be done in a separate thread to keep the GUI thread responsive. Report the progress to the GUI thread by calling PostMessage() with a message ID of WM_APP + x. You can pass the progress info with the wParam and lParam. Then only in the GUI thread you would actually update the progress bar to keep business logic and GUI clearly separated.

HttpSendRequest not posting correct

I am making a simple POST request using WinInet to an apache web server. I encode the data using base 64. The problem is that, every + character gets replaced by space character.
Why is that and how can I make correct POST request.
hInternet = InternetOpen(NULL, INTERNETOPENTYPEPRECONFIG, NULL, NULL, 0);
if (hInternet)
{
hConnect = InternetConnect(hInternet, szDomain, INTERNETDEFAULTHTTPPORT, NULL, NULL, INTERNETSERVICEHTTP, 0, dwTmp);
if (hConnect)
{
hRequest = HttpOpenRequest(hConnect, szPost, szExfiltrationURL, NULL, NULL,(char *)accept, INTERNETFLAGNOCACHEWRITE | INTERNETFLAGNOCOOKIES | INTERNETFLAGNOUI | INTERNETFLAGRELOAD, 0);
if (hRequest)
{
HttpSendRequest(hRequest, headers, lstrlen(headers), buffer, buflen);
InternetCloseHandle(hRequest);
}
InternetCloseHandle(hConnect);
}
InternetCloseHandle(hInternet);
}
From W3
"Within the query string, the plus sign is reserved as shorthand
notation for a space:
Source: "http://www.w3.org/Addressing/URL/4_URI_Recommentations.html"

How can I find if a webpage exists using WinINet

I am trying to connect and ensure various pages exist on a webserver provided by an instrument we design. I am trying to do this through C++ Win32 using WinInet commands.
I am happy that I have connected correctly to the webserver via HTTP:
hInternet = InternetOpen("Test", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0/*INTERNET_FLAG_ASYNC*/);
hhttp = InternetConnect(hInternet, "192.168.111.222", INTERNET_DEFAULT_HTTP_PORT, "admin", "admin", INTERNET_SERVICE_HTTP, 0, 0);
I believe I then have to open a request.
hHttpRequest = HttpOpenRequest(hhttp, "GET", "galogo.png", NULL, "192.168.111.222", lplpszAcceptTypes, INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE, 0);
and then send the request.
HttpSendRequest(hHttpRequest, NULL, 0, NULL, 0)
Note: 192.168.111.222 is the address of the unit running the webserver and galogo.png is an image displayed on the home page. Please also note that I am error checking between each statements so if I do disconnect the Ethernet then I do get a failure.
Initially I did try just connecting to the home.html page but this always passed so I thought I should try and get the image but I am probably lacking in knowledge. Other examples seem to then stream data but I wasn't sure if I needed to do this.
Most of the examples I have seen seem to show the HtppSendRequest in this format and I don't really understand about headers etc. Maybe it is here I am going wrong.
The HttpQueryInfo function will give header information relating to the request, and you can extract the HTTP status code from this.
You may be able to achieve the result more easily using higher level WinINet functions. I would suggest a sequence consisting of InternetOpen, InternetOpenUrl, HttpQueryInfo and then repeated calls to InternetReadFile if the HTTP status code is OK.
This Delphi code (from Delphi 7, so pre-Unicode) seems to do the job: -
function GetUrlContent(const Agent, Url: string): string;
var
NetHandle: HINTERNET;
UrlHandle: HINTERNET;
Buffer: array [0..1024] of Char;
BytesRead: DWORD;
Dummy: DWORD;
BufLen: DWORD;
HttpStatus: string;
begin
Result := '';
NetHandle := InternetOpen(PChar(Agent), INTERNET_OPEN_TYPE_PRECONFIG,
nil, nil, 0);
if Assigned(NetHandle) then
begin
UrlHandle := InternetOpenUrl(NetHandle, PChar(Url), nil, 0,
INTERNET_FLAG_RELOAD, 0);
if Assigned(UrlHandle) then
// UrlHandle valid? Proceed with download.
try
BufLen := Length(Buffer);
Dummy := 0;
// only get the file if the HTTP status code is 200
if HttpQueryInfo(UrlHandle, HTTP_QUERY_STATUS_CODE, #Buffer[0], BufLen, Dummy) then
begin
HttpStatus := Buffer;
if HttpStatus = '200' then
begin
FillChar(Buffer, SizeOf(Buffer), 0);
repeat
Result := Result + Buffer;
FillChar(Buffer, SizeOf(Buffer), 0);
InternetReadFile(UrlHandle, #Buffer, SizeOf(Buffer), BytesRead);
until BytesRead = 0;
end
else begin
raise Exception.CreateFmt('HTTP status code %s', [HttpStatus]);
end;
end
else begin
raise Exception.Create('Unable to read HTTP status code');
end;
finally
InternetCloseHandle(UrlHandle);
end
else begin
// UrlHandle is not valid. Raise an exception.
raise Exception.CreateFmt('Cannot open URL %s', [Url]);
end;
InternetCloseHandle(NetHandle);
end
else begin
// NetHandle is not valid. Raise an exception.
raise Exception.Create('Unable to initialize WinINet');
end;
end;
So, using a combination of cURL and Wireshark I'm finally there. I was making some fundamental mistakes but basically on the right track.
First open the connection and connect as previously stated, making sure it is not ASYNC (this lead to some overlapped IO errors):
hInternet = InternetOpen("Test", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0/*INTERNET_FLAG_ASYNC*/);
hhttp = InternetConnect(hInternet, "192.168.111.222", INTERNET_DEFAULT_HTTP_PORT, "admin", "admin", INTERNET_SERVICE_HTTP, 0, 0);
I needed to create the request and then send it. I only needed to specify the page as the request will take in connection details.
hHttpRequest = HttpOpenRequest(hhttp, "GET", "home.html", NULL, NULL, lplpszAcceptTypes, INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE, 0);
HttpSendRequest(hHttpRequest, NULL, 0, NULL, 0);
Then use the HttpQueryInfo function to retrieve the status and convert back to integer. Make sure you are sending the handle from the Request not the Connect.
//These are defined earlier
DWORD statCharLen = 0;
char statChar[256]="";
statCharLen = sizeof(statChar);
HttpQueryInfo(hHttpRequest, HTTP_QUERY_STATUS_CODE, statChar, &statCharLen, NULL);
Finally shut down connection:
InternetCloseHandle(hInternet)
Thanks
Its is Simple following are the Steps:
1- Open Connection
2- Connect
3- Open request
4- Send request
5- Read file
6- Save file (as png or jpg)
7- Close handles
The code is as follow:
#include <iostream>
#include <string>
#include <Windows.h>
#include <wininet.h>
#pragma comment(lib, "wininet")
using namespace std;
void download(string domain,string url,string filepath)
{
//Step 1:
HINTERNET hIntSession = InternetOpenA("MyApp", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
//Step 2:
HINTERNET hHttpSession = InternetConnectA(hIntSession, domain.c_str(), 80, 0, 0, INTERNET_SERVICE_HTTP, 0, NULL);
//Step 3:
HINTERNET hHttpRequest = HttpOpenRequestA( hHttpSession, "GET",url.c_str(),0, 0, 0, INTERNET_FLAG_RELOAD, 0);
TCHAR* szHeaders = L"";
CHAR szReq[1024] = "";
//Step 4:
if( !HttpSendRequest(hHttpRequest, szHeaders, wcslen(szHeaders), szReq, strlen(szReq))) {
DWORD dwErr = GetLastError();
cout<<"error "<<dwErr<<endl;
/// handle error
}
TCHAR szBuffer[1025];
DWORD dwRead=0;
FILE *f;
f=fopen(filepath.c_str(),"wb");
//Step 5 & 6:
while(InternetReadFile(hHttpRequest,szBuffer, 1024, &dwRead) && dwRead)
{
fwrite(szBuffer,sizeof(BYTE),1024,f);
dwRead=0;
}
fclose(f);
//Step 7:
InternetCloseHandle(hHttpRequest);
InternetCloseHandle(hHttpSession);
InternetCloseHandle(hIntSession);
}
int main()
{
download("www.stacktoheap.com","images/stackoverflow.png","C:\\Example\\example.png");
}