Converting WinHttp to WinInet API POST request - c++

I am trying to convert some HTTP request code from using the WinHttp COM interface to using lower-level WinInet calls from <wininet.h>. The COM version is working but I am having difficulty translating the calls into the WinInet API.
This code works fine and gets the correct error response (as the request data is empty) to the POST request:
#import <winhttpcom.dll>
#include <iostream>
#include <string>
int main()
{
HRESULT hr = CoInitialize(NULL);
using namespace WinHttp;
IWinHttpRequestPtr pReq = NULL;
hr = pReq.CreateInstance(__uuidof(WinHttpRequest));
const char* pszReq = "";
if (SUCCEEDED(hr))
{
_bstr_t bstrMethod("POST");
_bstr_t bstrUrl("https://lite.realtime.nationalrail.co.uk/OpenLDBWS/ldb9.asmx");
hr = pReq->Open(bstrMethod, bstrUrl);
pReq->SetRequestHeader(_bstr_t("Content-Type"), _bstr_t("text/*"));
_variant_t vReq(pszReq);
hr = pReq->Send(vReq);
if (SUCCEEDED(hr))
{
_bstr_t bstrResp;
hr = pReq->get_ResponseText(&bstrResp.GetBSTR());
if (SUCCEEDED(hr))
{
std::cout << std::string(bstrResp) << "\n";
}
}
}
CoUninitialize();
}
Saving the output as html, gives this rendering of the response (which is what I expect, since I haven't provided any request data, which would usually include an access token).
This is the code (amended after comments below, and should be reproducible) that I am using to try and replicate this result using wininet.h and the low-level Win32 calls (I realize I haven't closed the handles).
#include <windows.h>
#include <WinInet.h>
#include <iostream>
int main()
{
const char* pszReq = "";
const char* pszUrl = "https://lite.realtime.nationalrail.co.uk/OpenLDBWS/ldb9.asmx";
char szHostName[256];
char szPath[256];
URL_COMPONENTSA comps = {};
comps.dwStructSize = sizeof(comps);
comps.lpszHostName = szHostName;
comps.dwHostNameLength = sizeof(szHostName);
comps.lpszUrlPath = szPath;
comps.dwUrlPathLength = sizeof(szPath);
if (!InternetCrackUrlA(pszUrl, strlen(pszUrl), 0, &comps)) return 1;
HINTERNET hOpen = InternetOpenA("XYZ",INTERNET_OPEN_TYPE_DIRECT,NULL,NULL,0);
if (!hOpen) return 1;
HINTERNET hConnect = InternetConnectA(hOpen,szHostName,comps.nPort,
NULL,NULL,INTERNET_SERVICE_HTTP,0,NULL);
if (!hConnect) return 1;
const char * rgpszAcceptTypes[] = { "text/*", NULL };
HINTERNET hOpenReq = HttpOpenRequestA(hConnect,"POST",szPath,NULL, NULL,
rgpszAcceptTypes, 0,NULL);
if (!hOpenReq) return 1;
const char* pszHeader = "Content-Type: text/xml;charset=UTF-8";
//*** This line returns FALSE ***
BOOL bRet = HttpSendRequestA(hOpenReq, pszHeader, strlen(pszHeader), (LPVOID)pszReq, strlen(pszReq));
//*** LastError is ERROR_HTTP_INVALID_SERVER_RESPONSE
DWORD dwErr = GetLastError();
return 0;
}
All the WinInet handles are non-zero, suggesting the calls are working, but the last HttpSendRequestA() is returning FALSE immediately, with LastError set to ERROR_HTTP_INVALID_SERVER_RESPONSE.
Clearly the COM route hides a lot of intermediate working, and presumably some constants are defaulted to specific values. It may also be adding other header information, I suppose.
Perhaps someone can suggest where I am going wrong?

There are some mistakes in your WinInet code:
the pszServerName value needs to be just the host name by itself, not a full URL. If you have a URL as input, you can parse it into its constituent pieces using InternetCrackUrlA().
the 3rd parameter of HttpOpenRequestA() is the requested resource relative to pszServerName. So, in your example, you need to use "/" to request the root resource.
the 1st parameter of HttpSendRequestA() needs to be hOpenReq, not hOpen. Also, you should not be including the null-terminators in your buffer sizes.
If you have not already done so, you should have a look at WinInet's documentation on HTTP Sessions.
With that said, try this:
#include <windows.h>
#include <WinInet.h>
#include <iostream>
const char * pszUrl = "https://someUrl";
const char * pszReq = "A string of request data";
const char* pszHeader = "Content-Type: text/xml;charset=UTF-8";
char szHostName[256];
char szPath[256];
URL_COMPONENTSA comps = {};
comps.dwStructSize = sizeof(comps);
comps.lpszHostName = szHostName;
comps.dwHostNameLength = sizeof(szHostName);
comps.lpszUrlPath = szPath;
comps.dwUrlPathLength = sizeof(szPath);
BOOL bRet = InternetCrackUrlA(pszUrl, strlen(pszUrl), 0, &comps);
if (!bRet) ...
HINTERNET hOpen = InternetOpenA("XYZ", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (!hOpen) ...
HINTERNET hConnect = InternetConnectA(hOpen, szHostName, comps.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);
if (!hConnect) ...
HINTERNET hOpenReq = HttpOpenRequestA(hConnect, "POST", szPath, NULL, NULL, NULL, comps.nScheme == INTERNET_SCHEME_HTTPS ? INTERNET_FLAG_SECURE : 0, NULL);
if (!hOpenReq) ...
bRet = HttpSendRequestA(hOpenReq, pszHeader, strlen(pszHeader), pszReq, strlen(pszReq));
if (!bRet) ...
...

Related

How to make GET requests with WinInet for both HTTP and HTTPS? [duplicate]

I want to send HTTPS GET request using WinInet. As far as i know, i should do it just like sending HTTP request except i have to use INTERNET_DEFAULT_HTTPS_PORT and INTERNET_FLAG_SECURE flag.
So here is what i tried:
#include "stdafx.h"
#include <string>
#include <windows.h>
#include <WinInet.h>
#pragma comment (lib, "Wininet.lib")
using namespace std;
// convert string
wstring CharPToWstring(const char* _charP)
{
return wstring(_charP, _charP + strlen(_charP));
}
// send https request
wstring SendHTTPSRequest_GET(const wstring& _server,
const wstring& _page,
const wstring& _params = L"")
{
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, _server.c_str(), INTERNET_DEFAULT_HTTPS_PORT, NULL,NULL, INTERNET_SERVICE_HTTP, INTERNET_FLAG_SECURE, 1);
if (hConnect != NULL)
{
wstring request = _page +
(_params.empty() ? L"" : (L"?" + _params));
// open request
HINTERNET hRequest = ::HttpOpenRequest(hConnect, L"GET", (LPCWSTR)request.c_str() ,NULL, NULL, 0, INTERNET_FLAG_KEEP_CONNECTION, 1);
if (hRequest != NULL)
{
// send request
BOOL isSend = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0);
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;
}
int _tmain(int argc, _TCHAR* argv[])
{
wstring answer = SendHTTPSRequest_GET(L"www.site.com", L"page.php", L"param1=value1&param2=value2&param3=value3&param4=value4");
return 0;
}
And my function returned an answer:
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.0.10</center>
</body>
</html>
What am i doing wrong?
INTERNET_FLAG_SECURE should be used in the flags for HttpOpenRequest, not InternetConnect.
This is explained in the MSDN documentation for WinINet API flags:
INTERNET_FLAG_SECURE
0x00800000
Uses secure transaction semantics. This translates to using Secure Sockets Layer/Private Communications Technology (SSL/PCT) and is only meaningful in HTTP requests. This flag is used by HttpOpenRequest and InternetOpenUrl, but this is redundant if https:// appears in the URL. The InternetConnect function uses this flag for HTTP connections; all the request handles created under this connection will inherit this flag.

C++ expression must have class type on my class [duplicate]

This question already has answers here:
My attempt at value initialization is interpreted as a function declaration, and why doesn't A a(()); solve it?
(5 answers)
Closed 4 years ago.
I am really confused at the error 'expression must have class type' on newHTTP on line 13
#include "stdafx.h"
#include <iostream>
#include <string>
#include <windows.h>
#include <WinHttp.h>
#include "myHTTP.h"
int main()
{
WinHTTP newHTTP();
// error is here
HINTERNET myResponse = newHTTP.httpConnect(L"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36",
L"http://api",
0,
L"GET");
//
int x;
std::cin >> x;
return 0;
}
I just dont understand what im missing, i have specified HINTERNET on myresponse and made sure the method httpConnect returns a value. Can someone assist?
My class code (trimmed of course):
class WinHTTP {
private:
std::string siteUsername, sitePassword;
std::wstring UA, URL;
bool bResult = false;
DWORD dwSize = sizeof(DWORD); // used to handle reading data in bytes
LPSTR pszOutBuffer; // used to Allocate space for the buffer.
DWORD dwDownloaded = 0; // set to null if using asynchronously and use in callback function only
HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;
public:
WinHTTP(std::string myuser, std::string mypass) : siteUsername(myuser), sitePassword(mypass){
}
// TODO: update to be able to add proxy details either here or before. do check if proxy has been detected in here and open/connect accordingly
HINTERNET httpConnect(std::wstring userAgent, std::wstring myURL, int isHTTPS, std::wstring protocol) {
UA = userAgent;
URL = myURL;
std::wstring acceptTypes = L"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8";
int portToUse;
if (isHTTPS == 1) {
portToUse = 443;
}
else {
portToUse = 80;
}
//initialize http and return session handle -- use c_str to convert wstring to LPCWSTR
hSession = WinHttpOpen(UA.c_str(),
WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
//make the connection request
if (hSession) {
hConnect = WinHttpConnect(hSession, URL.c_str(), portToUse, 0);
}
else {
printf("error: %d",GetLastError());
}
// open the request - not connected at this point
hRequest = WinHttpOpenRequest(hConnect, protocol.c_str(), NULL, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
if (hRequest) {
return hRequest;
}
else {
printf("error: %d", GetLastError());
return hRequest;
}
}
};
Please add default constructor if you invoking one . I see only parametrized one .

How to use HTTP POST in C++ with Wininet

I am sending values of two variables using POST to the PHP server. The C++ application using Wininet connects to the server side script, but instead of sending the data correctly, it just shows empty fields in the parameters send using POST.
#include <windows.h> #include <wininet.h> #include <stdio.h> #include <fstream> #include <cstring>
#pragma comment (lib, "Wininet.lib")
#define SIZE 128
int main() {
HINTERNET Initialize, Connection, File;
DWORD dwBytes;
static const char *postData = "name=Jack+Din&age=38";
LPSTR accept1[2] = { "Accept: */*", NULL };
const char * const frmdata = "name=Arun+Pushkar&age=38";
static const char *hdrs[] = { "Content-Type: application/x-www-form-urlencoded" };
static const char *accept[] = { "*/*", NULL };
char ch;
Initialize = InternetOpen(L"HTTPGET", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (!Initialize)
{
printf("Failed to open session\n");
exit(0);
}
Connection = InternetConnect(Initialize, L"192.168.1.10", INTERNET_DEFAULT_HTTP_PORT,
NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
File = HttpOpenRequest(
Connection,
L"POST",
L"/trydata.php",
L"HTTP/1.0",
NULL,
NULL,
INTERNET_FLAG_NO_CACHE_WRITE,
0);
unsigned long dataLen = strlen((char*)frmdata)+1;
bool res = HttpSendRequest(
File, // file to which reply will come
(LPCWSTR)hdrs,
0,
(char*)frmdata, // post variables to be send
dataLen); // length of data send
if (res)
{
std::ofstream webSource;
webSource.open("a.html");
while (InternetReadFile(File, &ch, 1, &dwBytes))
{
if (dwBytes != 1)break;
webSource << ch;
}
webSource.close();
}
InternetCloseHandle(File);
InternetCloseHandle(Connection);
InternetCloseHandle(Initialize);
return 0;
}
The server side script is
<?php
$yourname = isset($_POST['name']) ? $_POST['name'] : 'no name';
$yourage = isset($_POST['age']) ? $_POST['age'] : 'no age';
echo "Hello".htmlspecialchars($yourname). "!";
echo "Your Age".htmlspecialchars($yourage). "!";
?>
When I run this C++ code I get the following in my a.html:
Hello no Name!Your Age no Age!
I would use ATL Server classes to do the same. Here is the example:
CAtlHttpClient client;
AtlNavigateData navData;
LPCSTR lpData = "data=toto";
navData.SetMethod(ATL_HTTP_METHOD_POST);
navData.SetPostData((BYTE*)lpData, lstrlenA(lpData), _T("application/x-www-form-urlencoded"));
client.Navigate(_T("mysite.net"), _T("myscript.php"), &navData);
const char* pszBody = (const char*)client.GetBody();

Passing Multiple params HttpPostRequest c++

I have this function to send HTTP POST requests in C++, but i am having a little problem passing multiple strings arguments to it. Here's my code so far:
#include <Windows.h>
int doHttpPost(char *szDomain, char *szPage, char *szPost)
{
int iReturn = 1;
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
static TCHAR hdrs[] = "Content-Type: application/x-www-form-urlencoded";
const char *accept[2]={"*/*", NULL};
TCHAR *frmdata = szPost;
hSession = InternetOpen("AGENT", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if(hSession)
{
hConnect = InternetConnect(hSession, szDomain, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
if(hConnect)
{
hRequest = HttpOpenRequest(hConnect, "POST", szPage, NULL, NULL, accept, INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD, 0);
if(hRequest)
{
if(HttpSendRequest(hRequest, hdrs, strlen(hdrs), frmdata, strlen(frmdata)))
iReturn = 0;
else
iReturn = 5;
}
else //HttpOpenRequest
iReturn = 3;
}
else //InternetConnect
iReturn = 2;
}
else //InternetOpen
iReturn = 1;
//Cleanup
InternetCloseHandle(hSession);
InternetCloseHandle(hConnect);
InternetCloseHandle(hRequest);
return iReturn;
}
I call the function this way:
doHttpPost((char*)"127.0.0.1",(char*)"/test/post.php",(char*)postreq);
Is there a way I can add multiple requests in the post field, such as:
value1=id&value2=password&value3=details
where id, password and details will be variables containing data.
You can build a string from parts using sprintf and friends.
The simplest code snippet, which however ignores escaping you need to do on the values, is as follows:
CHAR pszRequest[1024] = { 0 };
CHAR* pszValue1 = "id";
CHAR* pszValue2 = "password";
CHAR* pszValue3 = "details";
sprintf(pszRequest, "value1=%s&value2=%s&value3=%s",
pszValue1, pszValue2, pszValue3);
doHttpPost((char*) "127.0.0.1", (char*) "/test/post.php", pszRequest);
For a robust solution you need to read up on basic string manipulation functions, and check RFC that defines body format for "application/x-www-form-urlencoded" MIME type.

POST form data using WinInet c++

I'm trying to make this program connect to a website and submit form data in order to login, but I don't know what I'm doing wrong. I have heard of others like curl and Winsock but I chose the WinINet library. So just for the testing of this program I've been using the website Pastebin to post to. So far, I haven't seen any results from this. If this program succeeds in posting the form data it will give me the header to the location of the post on their site.
Am I writing the form data char* correctly? I have seen on other stackoverflow posts where they had a large amount of dashes before some number then the put their form data.
Do I need to add something to it make it simulate clicking the submit button?
Do I need to write out values for each elements on the form?
I have tried HttpAddRequestHeaders and that didn't help me.
Also, I get the ERROR_INSUFFICIENT_BUFFER error on HttpOpenRequest but it still returns a valid HINTERNET.
#include <Windows.h>
#include <WinInet.h>
#include <iostream>
#pragma comment( lib,"Wininet.lib")
using namespace std;
char* getheaders(HINTERNET hRequest){
DWORD dwInfoLevel=HTTP_QUERY_RAW_HEADERS_CRLF;
DWORD dwInfoBufferLength=10;
char* pInfoBuffer=(char*)malloc(dwInfoBufferLength+1);
while(!HttpQueryInfo(hRequest,dwInfoLevel,pInfoBuffer,&dwInfoBufferLength,NULL)){
if (GetLastError()==ERROR_INSUFFICIENT_BUFFER){
free(pInfoBuffer);
pInfoBuffer=(char*)malloc(dwInfoBufferLength+1);
}else{
fprintf(stderr,"HttpQueryInfo failed, error = %d (0x%x)\n",GetLastError(),GetLastError());
break;
}
}
pInfoBuffer[dwInfoBufferLength] = '\0';
return pInfoBuffer;
}
void readfile(HINTERNET hRequest,char** buffs,int size){
DWORD dwBytesAvailable;
DWORD dwBytesRead;
for(int i=0;i<size;i++){
if(!InternetQueryDataAvailable(hRequest,&dwBytesAvailable,0,0)) break;
buffs[i]=(char*)malloc(dwBytesAvailable+1);
bool bResult=InternetReadFile(hRequest,buffs[i],dwBytesAvailable,&dwBytesRead);
if(!bResult | dwBytesRead==0) break;
}
}
int main(int argc,char** argv){
char* hdrs="Content-Type: application/x-www-form-urlencoded";
char* frmdata="paste_code=test";
LPCSTR accept[2]={"*/*", NULL};
HINTERNET hSession = InternetOpen("http generic",INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
HINTERNET hConnect = InternetConnect(hSession, "www.pastebin.com",INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
HINTERNET hRequest = HttpOpenRequest(hConnect, "GET","/", NULL, NULL, accept, 0, 0);
//ERROR_INSUFFICIENT_BUFFER (122) with "accept".
bool send=HttpSendRequest(hRequest, hdrs, strlen(hdrs), NULL,NULL);
if(!send){
printf("HttpSendRequest failed, code=%d",GetLastError());
system("pause>nul");
return 0;
}
char* heads=getheaders(hRequest);
printf("%s\n\n\n\n",heads);
HINTERNET hRequest2 = HttpOpenRequest(hConnect, "POST","/", NULL, NULL, accept, 0, 0);
//ERROR_INSUFFICIENT_BUFFER (122) with "accept".
send=HttpSendRequest(hRequest2, hdrs, strlen(hdrs), frmdata,strlen(frmdata));
if(!send){
printf("HttpSendRequest failed, code=%d",GetLastError());
system("pause>nul");
return 0;
}
heads=getheaders(hRequest);
printf("%s\n\n\n\n",heads);
InternetCloseHandle(hRequest);
InternetCloseHandle(hRequest2);
InternetCloseHandle(hConnect);
InternetCloseHandle(hSession);
system("pause>nul");
return 0;
}
Your code is nearly correct, you must make sure of the following points:
char* hdrs="Content-Type: application/x-www-form-urlencoded";
you must make sure that your return object from POST message will be of type x-www-form-urlencoded or JSON . if it's JSON you
need to define char* hdrs="Content-Type: application/json\r\n";
Note: you must append \r\n to the hdrs.
try to call readFile method with buffer of size 10000 for example
and print buffer , it will print the output of the response to the
connection
In HINTERNET hRequest2 = HttpOpenRequest(hConnect, "POST","/", NULL, NULL, accept, 0, 0); instead of "/" you must call the path of
the requested API for example: .
In HINTERNET hRequest2 = HttpOpenRequest(hConnect, "POST", "/users/jsonlogin", NULL, NULL, accept, 0, 0);