Skip to content

Commit fd07b66

Browse files
committed
Remove curl dependency on Windows, use WinHttp instead
1 parent 9a7b987 commit fd07b66

9 files changed

Lines changed: 322 additions & 14 deletions

File tree

CMakeSettings.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"inheritEnvironments": [ "msvc_x86" ],
88
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
99
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
10-
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\" -DCURL_LIBRARY=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\lib\\libcurl.dll.a -DCURL_INCLUDE_DIR=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\include",
10+
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
1111
"buildCommandArgs": "-v",
1212
"ctestCommandArgs": ""
1313
},
@@ -18,7 +18,7 @@
1818
"inheritEnvironments": [ "msvc_x86" ],
1919
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
2020
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
21-
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\" -DCURL_LIBRARY=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\lib\\libcurl.dll.a -DCURL_INCLUDE_DIR=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\include",
21+
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
2222
"buildCommandArgs": "-v",
2323
"ctestCommandArgs": ""
2424
},
@@ -29,7 +29,7 @@
2929
"inheritEnvironments": [ "msvc_x64_x64" ],
3030
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
3131
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
32-
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\" -DCURL_LIBRARY=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\lib\\libcurl.dll.a -DCURL_INCLUDE_DIR=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\include",
32+
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
3333
"buildCommandArgs": "-v",
3434
"ctestCommandArgs": ""
3535
},
@@ -40,7 +40,7 @@
4040
"inheritEnvironments": [ "msvc_x64_x64" ],
4141
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
4242
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
43-
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\" -DCURL_LIBRARY=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\lib\\libcurl.dll.a -DCURL_INCLUDE_DIR=C:\\ProgramData\\chocolatey\\lib\\curl\\tools\\curl-7.61.0-win64-mingw\\include",
43+
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
4444
"buildCommandArgs": "-v",
4545
"ctestCommandArgs": ""
4646
}

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ Building on Windows (experimental)
7777
Build with Visual Studio 2017 and pass in the following flags to CMake:
7878

7979
-DDOKAN_LIB_PATH=[dokan library location, e.g. "C:\Program Files\Dokan\DokanLibrary-1.1.0"]
80-
-DCURL_LIBRARY=[path to libcurl.dll.a, e.g. "C:\ProgramData\chocolatey\lib\curl\tools\curl-7.61.1-win64-mingw\lib\libcurl.dll.a"]
81-
-DCURL_INCLUDE_DIR=[path to libcurl include files, e.g. "C:\ProgramData\chocolatey\lib\curl\tools\curl-7.61.1-win64-mingw\include"]
8280
-DBOOST_ROOT=[path to root of boost installation]
8381

8482
Troubleshooting

appveyor.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ configuration:
1212
- RelWithDebInfo
1313

1414
install:
15-
- choco install -y curl
1615
- choco install -y dokany --installargs ADDLOCAL=DokanDevFeature,DokanLibBFeature,DokanPDBFeature
1716
- cmake --version
1817

1918
build_script:
2019
- cmd: mkdir build
2120
- cmd: cd build
22-
- cmake .. -G "Visual Studio 15 2017 Win64" -DBUILD_TESTING=on -DBOOST_ROOT=C:\Libraries\boost_1_65_1 -DCURL_LIBRARY=C:\ProgramData\chocolatey\lib\curl\tools\curl-7.61.1-win64-mingw\lib\libcurl.dll.a -DCURL_INCLUDE_DIR=C:\ProgramData\chocolatey\lib\curl\tools\curl-7.61.1-win64-mingw\include -DDOKAN_LIB_PATH="C:\Program Files\Dokan\DokanLibrary-1.1.0"
21+
- cmake .. -G "Visual Studio 15 2017 Win64" -DBUILD_TESTING=on -DBOOST_ROOT=C:\Libraries\boost_1_65_1 -DDOKAN_LIB_PATH="C:\Program Files\Dokan\DokanLibrary-1.1.0"
2322
# TODO Make build parallel
2423
- cmd: cmake --build . --config %CONFIGURATION%
2524
- cmd: .\test\gitversion\%CONFIGURATION%\gitversion-test.exe

src/cpp-utils/CMakeLists.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ set(SOURCES
1515
tempfile/TempDir.cpp
1616
network/HttpClient.cpp
1717
network/CurlHttpClient.cpp
18+
network/WinHttpClient.cpp
1819
network/FakeHttpClient.cpp
1920
io/Console.cpp
2021
io/DontEchoStdinToStdoutRAII.cpp
@@ -54,9 +55,6 @@ set(SOURCES
5455

5556
add_library(${PROJECT_NAME} STATIC ${SOURCES})
5657

57-
find_package(CURL REQUIRED)
58-
target_include_directories(${PROJECT_NAME} PUBLIC ${CURL_INCLUDE_DIRS})
59-
target_link_libraries(${PROJECT_NAME} PUBLIC ${CURL_LIBRARIES})
6058

6159
if(NOT MSVC)
6260
find_package(Backtrace REQUIRED)
@@ -66,6 +64,14 @@ else()
6664
target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp)
6765
endif()
6866

67+
if (NOT MSVC)
68+
find_package(CURL REQUIRED)
69+
target_include_directories(${PROJECT_NAME} PUBLIC ${CURL_INCLUDE_DIRS})
70+
target_link_libraries(${PROJECT_NAME} PUBLIC ${CURL_LIBRARIES})
71+
else()
72+
target_link_libraries(${PROJECT_NAME} PUBLIC WinHttp)
73+
endif()
74+
6975
find_package(Threads REQUIRED)
7076
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
7177

src/cpp-utils/network/CurlHttpClient.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Base version taken from https://techoverflow.net/blog/2013/03/15/c-simple-http-download-using-libcurl-easy-api/
22

3+
#if !defined(_MSC_VER)
4+
35
#include "CurlHttpClient.h"
46
#include <sstream>
57
#include <iostream>
@@ -67,3 +69,5 @@ namespace cpputils {
6769
}
6870

6971
}
72+
73+
#endif

src/cpp-utils/network/CurlHttpClient.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#ifndef MESSMER_CPPUTILS_NETWORK_CURLHTTPCLIENT_HPP
33
#define MESSMER_CPPUTILS_NETWORK_CURLHTTPCLIENT_HPP
44

5+
#if !defined(_MSC_VER)
6+
57
#include "HttpClient.h"
68
#include "../macros.h"
79
#include <mutex>
@@ -42,3 +44,5 @@ namespace cpputils {
4244
}
4345

4446
#endif
47+
48+
#endif
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#if defined(_MSC_VER)
2+
3+
#include "WinHttpClient.h"
4+
#include <sstream>
5+
#include <iostream>
6+
#include <cpp-utils/assert/assert.h>
7+
#include <cpp-utils/data/Data.h>
8+
#include <codecvt>
9+
#include <Windows.h>
10+
#include <Winhttp.h>
11+
12+
using boost::none;
13+
using boost::optional;
14+
using std::string;
15+
using std::wstring;
16+
using std::wstring_convert;
17+
using std::ostringstream;
18+
19+
namespace cpputils {
20+
21+
namespace {
22+
struct HttpHandleRAII final {
23+
HINTERNET handle;
24+
25+
HttpHandleRAII(HINTERNET handle_) : handle(handle_) {}
26+
27+
HttpHandleRAII(HttpHandleRAII&& rhs) : handle(rhs.handle) {
28+
rhs.handle = nullptr;
29+
}
30+
31+
~HttpHandleRAII() {
32+
if (nullptr != handle) {
33+
BOOL success = WinHttpCloseHandle(handle);
34+
if (!success) {
35+
throw std::runtime_error("Error calling WinHttpCloseHandle. Error code: " + std::to_string(GetLastError()));
36+
}
37+
}
38+
}
39+
40+
DISALLOW_COPY_AND_ASSIGN(HttpHandleRAII);
41+
};
42+
43+
URL_COMPONENTS parse_url(const wstring &url) {
44+
URL_COMPONENTS result;
45+
result.dwStructSize = sizeof(result);
46+
// Declare fields we want. Setting a field to nullptr and the length to non-zero means the field will be returned.
47+
result.lpszScheme = nullptr;
48+
result.dwSchemeLength = 1;
49+
result.lpszHostName = nullptr;
50+
result.dwHostNameLength = 1;
51+
result.lpszUserName = nullptr;
52+
result.dwUserNameLength = 1;
53+
result.lpszPassword = nullptr;
54+
result.dwPasswordLength = 1;
55+
result.lpszUrlPath = nullptr;
56+
result.dwUrlPathLength = 1;
57+
result.lpszExtraInfo = nullptr;
58+
result.dwExtraInfoLength = 1;
59+
60+
BOOL success = WinHttpCrackUrl(url.c_str(), url.size(), ICU_REJECT_USERPWD, &result);
61+
if (!success) {
62+
throw std::runtime_error("Error parsing url '" + wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(url) + "'. Error code: " + std::to_string(GetLastError()));
63+
}
64+
65+
return result;
66+
}
67+
68+
INTERNET_PORT get_port_from_url(const URL_COMPONENTS& parsedUrl) {
69+
wstring scheme_str(parsedUrl.lpszScheme, parsedUrl.dwSchemeLength);
70+
string s_(wstring_convert < std::codecvt_utf8_utf16<wchar_t>>().to_bytes(scheme_str));
71+
if (parsedUrl.nScheme == INTERNET_SCHEME_HTTP) {
72+
ASSERT(scheme_str == L"http", "Scheme mismatch");
73+
if (parsedUrl.nPort != 80) {
74+
throw std::runtime_error("We don't support non-default ports");
75+
}
76+
return INTERNET_DEFAULT_HTTP_PORT;
77+
}
78+
else if (parsedUrl.nScheme == INTERNET_SCHEME_HTTPS) {
79+
ASSERT(scheme_str == L"https", "Scheme mismatch");
80+
if (parsedUrl.nPort != 443) {
81+
throw std::runtime_error("We don't support non-default ports");
82+
}
83+
return INTERNET_DEFAULT_HTTPS_PORT;
84+
}
85+
else {
86+
throw std::runtime_error("Unsupported scheme: " + wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(scheme_str));
87+
}
88+
}
89+
90+
class Request final {
91+
public:
92+
Request(HttpHandleRAII request) : request_(std::move(request)) {}
93+
94+
void set_redirect_policy(DWORD redirectPolicy) {
95+
BOOL success = WinHttpSetOption(request_.handle, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, sizeof(redirectPolicy));
96+
if (!success) {
97+
throw std::runtime_error("Error calling WinHttpSetOption. Error code: " + std::to_string(GetLastError()));
98+
}
99+
}
100+
101+
void set_timeouts(long timeoutMsec) {
102+
// TODO Timeout should be a total timeout, not per step as we're doing it here.
103+
BOOL success = WinHttpSetTimeouts(request_.handle, timeoutMsec, timeoutMsec, timeoutMsec, timeoutMsec);
104+
if (!success) {
105+
throw std::runtime_error("Error calling WinHttpSetTimeouts. Error code: " + std::to_string(GetLastError()));
106+
}
107+
}
108+
109+
void send() {
110+
BOOL success = WinHttpSendRequest(request_.handle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
111+
if (!success) {
112+
throw std::runtime_error("Error calling WinHttpSendRequest. Error code: " + std::to_string(GetLastError()));
113+
}
114+
}
115+
116+
void wait_for_response() {
117+
BOOL success = WinHttpReceiveResponse(request_.handle, nullptr);
118+
if (!success) {
119+
throw std::runtime_error("Error calling WinHttpReceiveResponse. Error code: " + std::to_string(GetLastError()));
120+
}
121+
}
122+
123+
DWORD get_status_code() {
124+
DWORD statusCode;
125+
DWORD statusCodeSize = sizeof(statusCode);
126+
BOOL success = WinHttpQueryHeaders(request_.handle, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX);
127+
if (!success) {
128+
throw std::runtime_error("Eror calling WinHttpQueryHeaders. Error code: " + std::to_string(GetLastError()));
129+
}
130+
return statusCode;
131+
}
132+
133+
string read_response() {
134+
ostringstream result;
135+
136+
while (true) {
137+
DWORD size = num_bytes_readable();
138+
if (size == 0) {
139+
break;
140+
}
141+
142+
cpputils::Data buffer(size + 1);
143+
buffer.FillWithZeroes();
144+
145+
DWORD num_read;
146+
BOOL success = WinHttpReadData(request_.handle, buffer.data(), buffer.size(), &num_read);
147+
if (!success) {
148+
throw std::runtime_error("Error calling WinHttpReadData. Error code: " + std::to_string(GetLastError()));
149+
}
150+
ASSERT(0 != num_read, "Weird behavior of WinHttpReadData.It should never read zero bytes since WinHttpQueryDataAvailable said there are bytes readable.");
151+
152+
result.write(reinterpret_cast<char*>(buffer.data()), num_read);
153+
ASSERT(result.good(), "Error writing to ostringstream");
154+
}
155+
156+
return result.str();
157+
}
158+
159+
private:
160+
DWORD num_bytes_readable() {
161+
DWORD result;
162+
BOOL success = WinHttpQueryDataAvailable(request_.handle, &result);
163+
if (!success) {
164+
throw std::runtime_error("Error calling WinHttpQueryDataAvailable. Error code: " + std::to_string(GetLastError()));
165+
}
166+
return result;
167+
}
168+
169+
HttpHandleRAII request_;
170+
};
171+
172+
struct Connection final {
173+
public:
174+
Connection(HttpHandleRAII connection) : connection_(std::move(connection)) {}
175+
176+
Request create_request(const URL_COMPONENTS& parsedUrl) {
177+
const INTERNET_PORT port = get_port_from_url(parsedUrl);
178+
const wstring path = wstring(parsedUrl.lpszUrlPath, parsedUrl.dwUrlPathLength) + wstring(parsedUrl.lpszExtraInfo, parsedUrl.dwExtraInfoLength);
179+
const DWORD flags = (port == INTERNET_DEFAULT_HTTPS_PORT) ? WINHTTP_FLAG_SECURE : 0;
180+
181+
HttpHandleRAII request_handle(WinHttpOpenRequest(connection_.handle, L"GET", path.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags));
182+
if (nullptr == request_handle.handle) {
183+
throw std::runtime_error("Error calling WinHttpOpenRequest. Error code: " + std::to_string(GetLastError()));
184+
}
185+
return Request(std::move(request_handle));
186+
}
187+
188+
private:
189+
HttpHandleRAII connection_;
190+
};
191+
}
192+
193+
struct WinHttpSession final {
194+
public:
195+
WinHttpSession(HttpHandleRAII session) : session_(std::move(session)) {}
196+
197+
Connection create_connection(const URL_COMPONENTS& parsedUrl) {
198+
const INTERNET_PORT port = get_port_from_url(parsedUrl);
199+
const wstring host(parsedUrl.lpszHostName, parsedUrl.dwHostNameLength);
200+
201+
HttpHandleRAII connection_handle = WinHttpConnect(session_.handle, host.c_str(), port, 0);
202+
if (nullptr == connection_handle.handle) {
203+
throw std::runtime_error("Error calling WinHttpConnect. Error code: " + std::to_string(GetLastError()));
204+
}
205+
206+
return Connection(std::move(connection_handle));
207+
}
208+
209+
private:
210+
HttpHandleRAII session_;
211+
};
212+
213+
namespace {
214+
std::unique_ptr<WinHttpSession> create_session() {
215+
HttpHandleRAII session_handle = WinHttpOpen(L"cpputils::HttpClient", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
216+
if(nullptr == session_handle.handle) {
217+
throw std::runtime_error("Error calling WinHttpOpen. Error code: " + std::to_string(GetLastError()));
218+
}
219+
220+
return std::make_unique<WinHttpSession>(std::move(session_handle));
221+
}
222+
}
223+
224+
WinHttpClient::WinHttpClient() : session_(create_session()) {}
225+
226+
WinHttpClient::~WinHttpClient() {}
227+
228+
string WinHttpClient::get(const string &url, optional<long> timeoutMsec) {
229+
wstring wurl = wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(url);
230+
const URL_COMPONENTS parsedUrl = parse_url(wurl);
231+
232+
ASSERT(parsedUrl.dwUserNameLength == 0, "Authentication not supported");
233+
ASSERT(parsedUrl.dwPasswordLength == 0, "Authentication not supported");
234+
235+
Connection connection = session_->create_connection(parsedUrl);
236+
Request request = connection.create_request(parsedUrl);
237+
238+
// allow redirects but not from https to http
239+
request.set_redirect_policy(WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP);
240+
241+
if (timeoutMsec != none) {
242+
request.set_timeouts(*timeoutMsec);
243+
}
244+
245+
request.send();
246+
request.wait_for_response();
247+
248+
DWORD statusCode = request.get_status_code();
249+
if (statusCode != HTTP_STATUS_OK) {
250+
throw std::runtime_error("HTTP Server returned unsupported status code: " + std::to_string(statusCode));
251+
}
252+
253+
return request.read_response();
254+
}
255+
256+
}
257+
258+
#endif

0 commit comments

Comments
 (0)