WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
HttpClient.cpp
Go to the documentation of this file.
1/*----------------------------------------------------------------------*\
2| This file is part of WoW Model Viewer |
3| |
4| WoW Model Viewer is free software: you can redistribute it and/or |
5| modify it under the terms of the GNU General Public License as |
6| published by the Free Software Foundation, either version 3 of the |
7| License, or (at your option) any later version. |
8| |
9| WoW Model Viewer is distributed in the hope that it will be useful, |
10| but WITHOUT ANY WARRANTY; without even the implied warranty of |
11| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12| GNU General Public License for more details. |
13| |
14| You should have received a copy of the GNU General Public License |
15| along with WoW Model Viewer. |
16| If not, see <http://www.gnu.org/licenses/>. |
17\*----------------------------------------------------------------------*/
18
19#include "HttpClient.h"
20
21#include <Windows.h>
22#include <winhttp.h>
23
24#include <string>
25#include <vector>
26
27#pragma comment(lib, "winhttp.lib")
28
29// ---------- helpers ----------
30
31static std::wstring Utf8ToWide(const std::string& s)
32{
33 if (s.empty()) return {};
34 const int len = MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), nullptr, 0);
35 std::wstring ws(len, L'\0');
36 MultiByteToWideChar(CP_UTF8, 0, s.data(), static_cast<int>(s.size()), ws.data(), len);
37 return ws;
38}
39
40// Crack a URL into components using WinHttpCrackUrl.
42{
43 std::wstring host;
44 std::wstring path; // includes query string
45 INTERNET_PORT port = 0;
46 bool isHttps = false;
47};
48
49static bool CrackUrl(const std::string& url, UrlParts& out)
50{
51 const std::wstring wurl = Utf8ToWide(url);
52
53 URL_COMPONENTS uc{};
54 uc.dwStructSize = sizeof(uc);
55 uc.dwHostNameLength = static_cast<DWORD>(-1);
56 uc.dwUrlPathLength = static_cast<DWORD>(-1);
57 uc.dwExtraInfoLength = static_cast<DWORD>(-1);
58
59 if (!WinHttpCrackUrl(wurl.c_str(), static_cast<DWORD>(wurl.size()), 0, &uc))
60 return false;
61
62 out.host.assign(uc.lpszHostName, uc.dwHostNameLength);
63 out.path.assign(uc.lpszUrlPath, uc.dwUrlPathLength);
64 if (uc.lpszExtraInfo && uc.dwExtraInfoLength)
65 out.path.append(uc.lpszExtraInfo, uc.dwExtraInfoLength);
66 out.port = uc.nPort;
67 out.isHttps = (uc.nScheme == INTERNET_SCHEME_HTTPS);
68 return true;
69}
70
71// ---------- public API ----------
72
73HttpClient::Response HttpClient::Get(const std::string& url, const ProgressCallback& progress)
74{
75 Response resp;
76
77 UrlParts parts;
78 if (!CrackUrl(url, parts))
79 {
80 resp.error = "Failed to parse URL: " + url;
81 return resp;
82 }
83
84 HINTERNET hSession = WinHttpOpen(L"WoWModelViewer",
85 WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
86 WINHTTP_NO_PROXY_NAME,
87 WINHTTP_NO_PROXY_BYPASS,
88 0);
89
90 if (!hSession)
91 {
92 resp.error = "WinHttpOpen failed";
93 return resp;
94 }
95
96 // Enable automatic redirect following
97 DWORD optionFlags = WINHTTP_OPTION_REDIRECT_POLICY_ALWAYS;
98 WinHttpSetOption(hSession, WINHTTP_OPTION_REDIRECT_POLICY, &optionFlags, sizeof(optionFlags));
99
100 HINTERNET hConnect = WinHttpConnect(hSession, parts.host.c_str(), parts.port, 0);
101 if (!hConnect)
102 {
103 resp.error = "WinHttpConnect failed";
104 WinHttpCloseHandle(hSession);
105 return resp;
106 }
107
108 const DWORD flags = parts.isHttps ? WINHTTP_FLAG_SECURE : 0;
109 HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET",
110 parts.path.c_str(), nullptr,
111 WINHTTP_NO_REFERER,
112 WINHTTP_DEFAULT_ACCEPT_TYPES,
113 flags);
114
115 if (!hRequest)
116 {
117 resp.error = "WinHttpOpenRequest failed";
118 WinHttpCloseHandle(hConnect);
119 WinHttpCloseHandle(hSession);
120 return resp;
121 }
122
123 if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
124 WINHTTP_NO_REQUEST_DATA, 0, 0, 0))
125 {
126 resp.error = "WinHttpSendRequest failed";
127 WinHttpCloseHandle(hRequest);
128 WinHttpCloseHandle(hConnect);
129 WinHttpCloseHandle(hSession);
130 return resp;
131 }
132
133 if (!WinHttpReceiveResponse(hRequest, nullptr))
134 {
135 resp.error = "WinHttpReceiveResponse failed";
136 WinHttpCloseHandle(hRequest);
137 WinHttpCloseHandle(hConnect);
138 WinHttpCloseHandle(hSession);
139 return resp;
140 }
141
142 // Read status code
143 DWORD statusCode = 0;
144 DWORD statusSize = sizeof(statusCode);
145 WinHttpQueryHeaders(hRequest,
146 WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
147 WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusSize, WINHTTP_NO_HEADER_INDEX);
148 resp.statusCode = static_cast<int>(statusCode);
149
150 // Read Content-Length (may be 0 if server doesn't provide it)
151 size_t totalBytes = 0;
152 {
153 wchar_t clBuf[32]{};
154 DWORD clSize = sizeof(clBuf);
155 if (WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH,
156 WINHTTP_HEADER_NAME_BY_INDEX, clBuf, &clSize, WINHTTP_NO_HEADER_INDEX))
157 {
158 totalBytes = static_cast<size_t>(_wtoi64(clBuf));
159 }
160 }
161
162 // Read body
163 std::vector<char> buffer;
164 size_t received = 0;
165 DWORD bytesAvailable = 0;
166 while (WinHttpQueryDataAvailable(hRequest, &bytesAvailable) && bytesAvailable > 0)
167 {
168 buffer.resize(bytesAvailable);
169 DWORD bytesRead = 0;
170 if (WinHttpReadData(hRequest, buffer.data(), bytesAvailable, &bytesRead))
171 {
172 resp.body.append(buffer.data(), bytesRead);
173 received += bytesRead;
174 if (progress)
175 progress(received, totalBytes);
176 }
177 }
178
179 WinHttpCloseHandle(hRequest);
180 WinHttpCloseHandle(hConnect);
181 WinHttpCloseHandle(hSession);
182
183 resp.success = (resp.statusCode >= 200 && resp.statusCode < 300);
184 if (!resp.success && resp.error.empty())
185 resp.error = "HTTP " + std::to_string(resp.statusCode);
186
187 return resp;
188}
static bool CrackUrl(const std::string &url, UrlParts &out)
static std::wstring Utf8ToWide(const std::string &s)
Response Get(const std::string &url, const ProgressCallback &progress=nullptr)
Perform a synchronous HTTP(S) GET request.
std::function< void(size_t bytesReceived, size_t totalBytes)> ProgressCallback
Optional progress callback: (bytesReceived, totalBytes). totalBytes may be 0 if unknown.
Definition HttpClient.h:40
Simple HTTP response containing status, body, and error info.
Definition HttpClient.h:32
bool success
True if the request completed without error.
Definition HttpClient.h:36
std::string body
Response body.
Definition HttpClient.h:34
std::string error
Error message (empty on success).
Definition HttpClient.h:35
int statusCode
HTTP status code (e.g. 200, 404).
Definition HttpClient.h:33
std::wstring path
INTERNET_PORT port
bool isHttps
std::wstring host