Skip to content

Commit 2e32519

Browse files
author
Dan Dees
committed
winhttp test program
1 parent 6250b39 commit 2e32519

File tree

3 files changed

+371
-0
lines changed

3 files changed

+371
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ add_subdirectory(3rdparty)
1212
add_subdirectory(a)
1313
add_subdirectory(MemoryModule)
1414
add_subdirectory(test)
15+
add_subdirectory(winhttp)

winhttp/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
cmake_minimum_required(VERSION 3.27)
2+
project(winhttp-test LANGUAGES CXX)
3+
4+
set(exename "winhttp-test")
5+
add_executable(${exename})
6+
target_link_libraries(${exename} PRIVATE
7+
MemoryModulePP-shared
8+
shlwapi # PathFindOnPathW
9+
)
10+
11+
# use install directory under build to assemble tests
12+
set( INSTALL_DIR "${CMAKE_BINARY_DIR}/install")
13+
14+
# common sources and libs
15+
target_sources(${exename} PRIVATE winhttp.cpp)
16+
17+
# copy exes into install dir
18+
install(TARGETS ${exename} RUNTIME DESTINATION ${INSTALL_DIR})
19+
20+
# add shared lib to PATH
21+
file(TO_NATIVE_PATH "$<TARGET_FILE_DIR:MemoryModulePP-shared>" MMSHARED_DIR)
22+
set_target_properties(${exename} PROPERTIES
23+
VS_DEBUGGER_ENVIRONMENT
24+
"PATH=${MMSHARED_DIR};%PATH%"
25+
)

winhttp/winhttp.cpp

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
#include <expected>
2+
#include <filesystem>
3+
#include <system_error>
4+
#include <windows.h>
5+
#include <fstream>
6+
#include <print>
7+
#include <format>
8+
#include <array>
9+
#include <shlwapi.h>
10+
#include <iostream>
11+
#include <winhttp.h>
12+
#include <vector>
13+
#include <stdexcept>
14+
#include <memory>
15+
#include <LoadDllMemoryApi.h>
16+
17+
// Find DLL in system path, returning std::filesystem::path with single return
18+
[[nodiscard]] static std::filesystem::path FindDllInPath(const std::wstring& dllName) noexcept
19+
{
20+
std::array<wchar_t, MAX_PATH> fullPath{};
21+
return dllName.size() < fullPath.size()
22+
&& wcscpy_s(fullPath.data(), fullPath.size(), dllName.c_str()) == 0
23+
&& PathFindOnPathW(fullPath.data(), nullptr)
24+
? std::filesystem::path(fullPath.data())
25+
: std::filesystem::path{};
26+
}
27+
28+
// Returns a vector containing the DLL data or an error code
29+
[[nodiscard]] static std::expected<std::vector<char>, std::error_code>
30+
ReadDllToMemory(const std::filesystem::path& filePath) {
31+
std::expected<std::vector<char>, std::error_code> result;
32+
33+
// Open the file in binary mode with RAII
34+
std::ifstream dllFile(filePath, std::ios::binary | std::ios::ate);
35+
if (!dllFile.is_open()) {
36+
result = std::unexpected(std::make_error_code(std::errc::no_such_file_or_directory));
37+
} else {
38+
// Go to end to get size
39+
const auto streampos = dllFile.tellg();
40+
41+
// Check size
42+
if (streampos <= 0) {
43+
result = std::unexpected(std::make_error_code(std::errc::no_such_file_or_directory));
44+
} else if (streampos > SIZE_MAX) {
45+
result = std::unexpected(std::make_error_code(std::errc::file_too_large));
46+
} else {
47+
// Report size
48+
const auto fileSize = static_cast<size_t>(streampos);
49+
std::cout << std::format(" {} bytes for {}\n", fileSize, filePath.string());
50+
51+
// Allocate buffer
52+
std::vector<char> buffer(fileSize);
53+
if (buffer.empty()) {
54+
std::cerr << std::format(" Failed to allocate {} bytes in std::vector for {}\n", fileSize, filePath.string());
55+
result = std::unexpected(std::make_error_code(std::errc::not_enough_memory));
56+
} else {
57+
// Reset to beginning of file
58+
dllFile.seekg(0, std::ios::beg);
59+
60+
// Read entire file
61+
result = dllFile.read(buffer.data(), fileSize)
62+
? std::expected<std::vector<char>, std::error_code>(std::move(buffer)) // Success case: assign buffer to result
63+
: std::unexpected(std::make_error_code(std::errc::io_error));
64+
}
65+
}
66+
}
67+
68+
return result;
69+
}
70+
71+
// Struct to manage WinHTTP function pointers with upfront loading
72+
struct EnsureDll {
73+
private:
74+
HMODULE hWinhttp; // DLL handle
75+
std::vector<char> buffer; // Memory buffer for DLL
76+
77+
// Template to get function pointer
78+
template <typename TT>
79+
void GetProc(LPCSTR proc_name, TT& target) {
80+
target = reinterpret_cast<TT>(GetProcAddress(hWinhttp, proc_name));
81+
if (!target) {
82+
throw std::runtime_error(std::format("Failed to get function: {}", proc_name));
83+
}
84+
}
85+
86+
public:
87+
// Instance function pointers
88+
HINTERNET(WINAPI *fnWinHttpOpen)(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD);
89+
HINTERNET(WINAPI *fnWinHttpConnect)(HINTERNET, LPCWSTR, INTERNET_PORT, DWORD);
90+
HINTERNET(WINAPI *fnWinHttpOpenRequest)(HINTERNET, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR*, DWORD);
91+
BOOL(WINAPI *fnWinHttpSendRequest)(HINTERNET, LPCWSTR, DWORD, LPVOID, DWORD, DWORD, DWORD_PTR);
92+
BOOL(WINAPI *fnWinHttpReceiveResponse)(HINTERNET, LPVOID);
93+
BOOL(WINAPI *fnWinHttpQueryDataAvailable)(HINTERNET, LPDWORD);
94+
BOOL(WINAPI *fnWinHttpReadData)(HINTERNET, LPVOID, DWORD, LPDWORD);
95+
BOOL(WINAPI *fnWinHttpCloseHandle)(HINTERNET);
96+
97+
explicit EnsureDll(const std::filesystem::path& dllPath) : hWinhttp(nullptr) {
98+
99+
// Load DLL into memory
100+
const auto& result = ReadDllToMemory(dllPath);
101+
if (!result) {
102+
const auto errorMsg = std::system_category().message(result.error().value());
103+
throw std::runtime_error(std::format("Failed to load DLL: {}", errorMsg));
104+
}
105+
106+
buffer = std::move(*result);
107+
108+
// Load DLL from memory
109+
hWinhttp = LoadLibraryMemory(buffer.data());
110+
if (!hWinhttp) {
111+
buffer.clear(); // Release memory
112+
throw std::runtime_error("LoadLibraryMemory failed");
113+
}
114+
115+
// Load all function pointers using template
116+
try {
117+
GetProc("WinHttpOpen", fnWinHttpOpen);
118+
GetProc("WinHttpConnect", fnWinHttpConnect);
119+
GetProc("WinHttpOpenRequest", fnWinHttpOpenRequest);
120+
GetProc("WinHttpSendRequest", fnWinHttpSendRequest);
121+
GetProc("WinHttpReceiveResponse", fnWinHttpReceiveResponse);
122+
GetProc("WinHttpQueryDataAvailable", fnWinHttpQueryDataAvailable);
123+
GetProc("WinHttpReadData", fnWinHttpReadData);
124+
GetProc("WinHttpCloseHandle", fnWinHttpCloseHandle);
125+
}
126+
catch (...) {
127+
FreeLibraryMemory(hWinhttp);
128+
buffer.clear(); // Release memory
129+
throw;
130+
}
131+
}
132+
133+
~EnsureDll() {
134+
if (hWinhttp) {
135+
FreeLibraryMemory(hWinhttp);
136+
}
137+
buffer.clear(); // Memory is managed by vector
138+
}
139+
};
140+
141+
// Struct to manage HINTERNET handles with RAII
142+
struct WindowHandleManager {
143+
HINTERNET handle;
144+
EnsureDll& api;
145+
146+
WindowHandleManager(HINTERNET h, EnsureDll& a) : handle(h), api(a) {}
147+
~WindowHandleManager() {
148+
if (handle && api.fnWinHttpCloseHandle) {
149+
api.fnWinHttpCloseHandle(handle);
150+
handle = nullptr;
151+
}
152+
}
153+
154+
// Access handle
155+
HINTERNET get() const { return handle; }
156+
157+
// Prevent copying
158+
WindowHandleManager(const WindowHandleManager&) = delete;
159+
WindowHandleManager& operator=(const WindowHandleManager&) = delete;
160+
161+
// Allow moving
162+
WindowHandleManager(WindowHandleManager&& other) noexcept : handle(other.handle), api(other.api) {
163+
other.handle = nullptr;
164+
}
165+
WindowHandleManager& operator=(WindowHandleManager&& other) noexcept {
166+
if (this != &other) {
167+
if (handle && api.fnWinHttpCloseHandle) {
168+
api.fnWinHttpCloseHandle(handle);
169+
}
170+
handle = other.handle;
171+
other.handle = nullptr;
172+
}
173+
return *this;
174+
}
175+
};
176+
177+
static int test(const std::filesystem::path& dllFullPath, const std::wstring& serverName, int serverPort, const std::wstring& objectName)
178+
{
179+
std::wcout << std::format(L"Using DLL at: {}\n", dllFullPath.wstring());
180+
181+
// Initialize API with DLL path
182+
EnsureDll api(dllFullPath);
183+
try {
184+
EnsureDll api(dllFullPath);
185+
} catch (const std::runtime_error& e) {
186+
std::print("Error: {}\n", e.what());
187+
return -1;
188+
}
189+
190+
// Session scope
191+
WindowHandleManager hSession(
192+
api.fnWinHttpOpen(
193+
L"A WinHTTP Example Program/1.0",
194+
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
195+
WINHTTP_NO_PROXY_NAME,
196+
WINHTTP_NO_PROXY_BYPASS,
197+
0),
198+
api);
199+
if (!hSession.get()) {
200+
std::print("Error {} in WinHttpOpen.\n", GetLastError());
201+
return -1;
202+
}
203+
204+
// Connection scope
205+
{
206+
WindowHandleManager hConnect(
207+
api.fnWinHttpConnect(
208+
hSession.get(),
209+
serverName.c_str(),
210+
static_cast<INTERNET_PORT>(serverPort),
211+
0),
212+
api);
213+
if (!hConnect.get()) {
214+
std::print("Error {} in WinHttpConnect.\n", GetLastError());
215+
return -1;
216+
}
217+
218+
// Request scope
219+
{
220+
WindowHandleManager hRequest(
221+
api.fnWinHttpOpenRequest(
222+
hConnect.get(),
223+
L"GET",
224+
objectName.c_str(),
225+
nullptr,
226+
WINHTTP_NO_REFERER,
227+
WINHTTP_DEFAULT_ACCEPT_TYPES,
228+
0),
229+
api);
230+
if (!hRequest.get()) {
231+
std::print("Error {} in WinHttpOpenRequest.\n", GetLastError());
232+
return -1;
233+
}
234+
235+
// Send HTTP request
236+
auto bResults = api.fnWinHttpSendRequest(
237+
hRequest.get(),
238+
WINHTTP_NO_ADDITIONAL_HEADERS,
239+
0,
240+
WINHTTP_NO_REQUEST_DATA,
241+
0,
242+
0,
243+
0);
244+
if (!bResults) {
245+
std::print("Error {} in WinHttpSendRequest.\n", GetLastError());
246+
return -1;
247+
}
248+
249+
// End the HTTP request
250+
bResults = api.fnWinHttpReceiveResponse(hRequest.get(), nullptr);
251+
if (!bResults) {
252+
std::print("Error {} in WinHttpReceiveResponse.\n", GetLastError());
253+
return -1;
254+
}
255+
256+
// Allocate memory for the response
257+
DWORD dwSize = 0;
258+
do {
259+
// Check for available data
260+
dwSize = 0;
261+
if (!api.fnWinHttpQueryDataAvailable(hRequest.get(), &dwSize)) {
262+
std::print("Error {} in WinHttpQueryDataAvailable.\n", GetLastError());
263+
return -1;
264+
}
265+
266+
// Allocate memory for the buffer
267+
std::vector<unsigned char> pszOutBuffer(dwSize + 1);
268+
269+
// Read the response data
270+
ZeroMemory(pszOutBuffer.data(), dwSize + 1);
271+
DWORD dwDownloaded = 0;
272+
if (!api.fnWinHttpReadData(hRequest.get(), pszOutBuffer.data(), dwSize, &dwDownloaded)) {
273+
std::print("Error {} in WinHttpReadData.\n", GetLastError());
274+
return -1;
275+
}
276+
else {
277+
// Convert buffer to string for printing
278+
std::string response(reinterpret_cast<char*>(pszOutBuffer.data()), dwDownloaded);
279+
std::print("Response: {}\n", response);
280+
}
281+
} while (dwSize > 0);
282+
} // hRequest destroyed here
283+
} // hConnect destroyed here
284+
285+
std::print("Press any key to close...\n");
286+
getchar();
287+
288+
return 0;
289+
} // hSession destroyed here
290+
291+
// Example usage
292+
extern "C" int main(int argc, char* argv[]) {
293+
std::wstring dll_name(L"winhttp.dll");
294+
const std::filesystem::path dllPath{dll_name};
295+
296+
// Find DLL in system path
297+
const auto dllFullPath = FindDllInPath(dll_name);
298+
if (dllFullPath.empty()) {
299+
std::wcout << std::format(L"DLL not found: {}\n", dll_name);
300+
return 1;
301+
}
302+
303+
std::wcout << std::format(L"Found {} for {}\n", dllFullPath.wstring(), dll_name);
304+
305+
// Default values
306+
std::wstring serverName = L"neverssl.com";
307+
int serverPort = 80;
308+
std::wstring objectName = L"/";
309+
310+
// Parse command-line arguments
311+
if (argc > 1 && argv[1]) {
312+
// Convert serverName from char* to wstring
313+
size_t convertedChars = 0;
314+
wchar_t wServerName[256];
315+
mbstowcs_s(&convertedChars, wServerName, argv[1], strlen(argv[1]) + 1);
316+
if (convertedChars > 0) {
317+
serverName = wServerName;
318+
}
319+
}
320+
321+
if (argc > 2 && argv[2]) {
322+
try {
323+
serverPort = std::stoi(argv[2]);
324+
if (serverPort <= 0 || serverPort > 65535) {
325+
std::print("Invalid port: {}. Using default: 80\n", argv[2]);
326+
serverPort = 80;
327+
}
328+
} catch (const std::exception& e) {
329+
std::print("Invalid port: {}. Using default: 80\n", argv[2]);
330+
serverPort = 80;
331+
}
332+
}
333+
334+
if (argc > 3 && argv[3]) {
335+
// Convert objectName from char* to wstring
336+
size_t convertedChars = 0;
337+
wchar_t wObjectName[256];
338+
mbstowcs_s(&convertedChars, wObjectName, argv[3], strlen(argv[3]) + 1);
339+
if (convertedChars > 0) {
340+
objectName = wObjectName;
341+
}
342+
}
343+
344+
return test(dllFullPath, serverName, serverPort, objectName);
345+
}

0 commit comments

Comments
 (0)