Skip to content

Instantly share code, notes, and snippets.

@daiakushi
Last active September 4, 2023 02:31
Show Gist options
  • Save daiakushi/0d1b6971b7ca0bf3cb134098acebbf65 to your computer and use it in GitHub Desktop.
Save daiakushi/0d1b6971b7ca0bf3cb134098acebbf65 to your computer and use it in GitHub Desktop.
A simple web client implemented by WinHTTP service APIs
#include <windows.h>
#include <winhttp.h>
#include <array>
#include <iostream>
#include <string>
#pragma comment(lib, "Winhttp.lib")
using namespace std;
/*
* ref. https://stackoverflow.com/questions/2616011/easy-way-to-parse-a-url-in-c-cross-platform
*/
struct Uri {
wstring QueryString, Path, Protocol, Host, PortStr;
unsigned short PortBin;
static Uri Parse(const wstring &uri) {
Uri result;
typedef wstring::const_iterator iterator_t;
if (uri.length() == 0) return result;
// get query start
iterator_t queryStart = find(uri.begin(), uri.end(), L'?');
// protocol
iterator_t protocolStart = uri.begin();
iterator_t protocolEnd = find(protocolStart, uri.end(), L':'); // "://"
if (protocolEnd != uri.end()) {
wstring prot = &*(protocolEnd);
if ((prot.length() > 3) && (prot.substr(0, 3) == L"://")) {
result.Protocol = wstring(protocolStart, protocolEnd);
protocolEnd += 3; // "://""
} else
protocolEnd = uri.begin(); // no protocol
} else
protocolEnd = uri.begin(); // no protocol
// host
iterator_t hostStart = protocolEnd;
iterator_t pathStart = find(hostStart, uri.end(), L'/'); // get pathStart
iterator_t hostEnd = find(protocolEnd, (pathStart != uri.end()) ?
pathStart : queryStart, L':'); // check for port
result.Host = wstring(hostStart, hostEnd);
// port
if ((hostEnd != uri.end()) && ((&*(hostEnd))[0] == L':')) {
hostEnd++;
iterator_t portEnd = (pathStart != uri.end()) ? pathStart : queryStart;
result.PortStr = wstring(hostEnd, portEnd);
result.PortBin = stoi(result.PortStr.c_str());
} else {
for (const auto& [proto, port] : proto_port)
if (wcsnicmp(result.Protocol.c_str(), proto.c_str(), proto.length()) == 0) {
result.PortBin = port;
break;
}
}
// path
if (pathStart != uri.end())
result.Path = wstring(pathStart, queryStart);
// query
if (queryStart != uri.end())
result.QueryString = wstring(queryStart, uri.end());
return result;
} // Parse
private:
static inline array<pair<wstring, int>, 2> proto_port = {
make_pair<>(wstring(L"https"), 443),
make_pair<>(wstring(L"http"), 80)
};
}; // uri
static size_t _iconv(const string& from, wstring &to) {
const char *str = from.c_str();
size_t retSize = 0, len = from.length();
wchar_t *wstr = new wchar_t[len + 1];
if (!wstr) return 0;
SecureZeroMemory(wstr, sizeof(wchar_t) * (len + 1));
errno_t err = mbstowcs_s(&retSize, wstr, len + 1, str, len);
if (0 == err) to.assign(wstr);
delete [] wstr;
return 0 == err ? retSize : 0;
}
static void _usage(const char* app) {
cout << "Usage : " << app << " <URL> [--no-cert-check]" << endl;
}
int SimpleHttpsClient (
const wstring& Host,
const unsigned short& Port,
const wstring& Uri,
bool useHttps = false,
bool ignoreSslCert = false
)
{
HINTERNET hSession = 0;
HINTERNET hConnect = 0;
HINTERNET hRequest = 0;
hSession = WinHttpOpen(
L"Simple WinHttp Client/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) {
cerr << "WinHttpOpen() error (" << GetLastError() << ")\n";
goto error;
}
hConnect = WinHttpConnect(hSession, Host.c_str(), Port, 0);
if (!hConnect) {
cerr << "WinHttpConnect() error (" << GetLastError() << ")\n";
goto error;
}
hRequest = WinHttpOpenRequest(
hConnect, L"GET", Uri.c_str(), NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, useHttps ? WINHTTP_FLAG_SECURE : 0);
if (!hRequest) {
cerr << "WinHttpOpenRequest() error (" << GetLastError() << ")\n";
goto error;
}
bool retry;
do {
retry = false;
DWORD result = NO_ERROR;
if (WinHttpSendRequest(
hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0) == FALSE
)
{
result = GetLastError();
// try again if Certificate is invaid yet client allows it
if (result == ERROR_WINHTTP_SECURE_FAILURE && ignoreSslCert) {
DWORD dwFlags = SECURITY_FLAG_IGNORE_UNKNOWN_CA |
SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE |
SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
if (WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags)))
retry = true;
} else if (result == ERROR_WINHTTP_RESEND_REQUEST) {
/*
* Some authentication schemes require multiple transactions WinHttpSendRequest could return the
* error, ERROR_WINHTTP_RESEND_REQUEST. When this error is encountered, the application should
* continue to resend the request until a response is received that does not contain a 401 or 407
* status code. A 200 status code indicates that the resource is available and the request is
* successful.
*
* ref. http://msdn.microsoft.com/en-us/library/windows/desktop/aa383144%28v=vs.85%29.aspx
*/
retry = true;
}
}
} while (retry);
if (!WinHttpReceiveResponse(hRequest, NULL)) {
cerr << "WinHttpReceiveResponse() error (" << GetLastError() << ")\n";
goto error;
}
DWORD dwAvailable = 0, dwReceived = 0, dwTotal = 0;
/*
* ... you could instead start with an appropriately-sized buffer (perhaps 1 megabyte), and resize that
* if necessary. In practice, WinHttpQueryDataAvailable returns no more than 8 kilobytes.
*
* ref. https://learn.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpquerydataavailable
*/
array<char, 8 * 1024> buffer;
char *pBuffer = &buffer[0];
while (WinHttpQueryDataAvailable(hRequest, &dwAvailable) && dwAvailable) {
WinHttpReadData(hRequest, pBuffer, dwAvailable, &dwReceived);
cout << pBuffer;
dwTotal += dwReceived;
dwReceived = 0;
buffer.fill(0);
}
cout << "\nReceived " << dwTotal << " bytes data.\n";
error:
if (hRequest) WinHttpCloseHandle(hRequest);
if (hConnect) WinHttpCloseHandle(hConnect);
if (hSession) WinHttpCloseHandle(hSession);
return 0;
}
int main(int argc, char *argv[])
{
wstring request;
bool useHttps = false, ignoreCert = false;
if (argc < 2) {
_usage(argv[0]);
return 0;
}
_iconv(argv[1], request);
Uri query = Uri::Parse(request);
if (0 == wcsnicmp(query.Protocol.c_str(), L"https", 5))
useHttps = true;
if (argc >= 3) {
if (0 == strnicmp(argv[2], "--no-cert-check", 16)) {
ignoreCert = true;
} else {
cout << "Unknown parameter " << argv[2] << endl;
_usage(argv[0]);
return 0;
}
}
#ifdef _DEBUG
wcout << (useHttps ? L"https://" : L"http://") << query.Host << L":" << query.PortBin
<< query.Path << L" " << (ignoreCert ? L"(no-cert-check)" : L"(cert-check)") << endl;
#endif
return SimpleHttpsClient(query.Host, query.PortBin, query.Path, useHttps, ignoreCert);
}
// cl /EHsc /Ox /std:c++17 winHttpsClient.cpp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment