Last active
September 4, 2023 02:31
-
-
Save daiakushi/0d1b6971b7ca0bf3cb134098acebbf65 to your computer and use it in GitHub Desktop.
A simple web client implemented by WinHTTP service APIs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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