Merge
This commit is contained in:
commit
ddffc2c83a
@ -1,19 +1,14 @@
|
|||||||
{
|
{
|
||||||
"environments": [
|
|
||||||
{
|
|
||||||
"BuildDir": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "x86-Debug",
|
"name": "x86-Debug",
|
||||||
"generator": "Ninja",
|
"generator": "Ninja",
|
||||||
"configurationType": "Debug",
|
"configurationType": "Debug",
|
||||||
"inheritEnvironments": [ "msvc_x86" ],
|
"inheritEnvironments": [ "msvc_x86" ],
|
||||||
"buildRoot": "${env.BuildDir}\\${name}",
|
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
|
||||||
"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",
|
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
|
||||||
"buildCommandArgs": "",
|
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
|
||||||
|
"buildCommandArgs": "-v",
|
||||||
"ctestCommandArgs": ""
|
"ctestCommandArgs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -21,31 +16,33 @@
|
|||||||
"generator": "Ninja",
|
"generator": "Ninja",
|
||||||
"configurationType": "RelWithDebInfo",
|
"configurationType": "RelWithDebInfo",
|
||||||
"inheritEnvironments": [ "msvc_x86" ],
|
"inheritEnvironments": [ "msvc_x86" ],
|
||||||
"buildRoot": "${env.BuildDir}\\${name}",
|
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
|
||||||
"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",
|
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
|
||||||
"buildCommandArgs": "",
|
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
|
||||||
|
"buildCommandArgs": "-v",
|
||||||
"ctestCommandArgs": ""
|
"ctestCommandArgs": ""
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "x64-Debug",
|
"name": "x64-Debug",
|
||||||
"generator": "Ninja",
|
"generator": "Ninja",
|
||||||
"configurationType": "Debug",
|
"configurationType": "Debug",
|
||||||
"inheritEnvironments": [ "msvc_x64" ],
|
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||||
"buildRoot": "${env.BuildDir}\\${name}",
|
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
|
||||||
"cmakeCommandArgs": "-DBUILD_TESTING=on -DBOOST_ROOT=C:\\Boost -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",
|
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
|
||||||
"buildCommandArgs": "",
|
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
|
||||||
|
"buildCommandArgs": "-v",
|
||||||
"ctestCommandArgs": ""
|
"ctestCommandArgs": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "x64-Release",
|
"name": "x64-Release",
|
||||||
"generator": "Ninja",
|
"generator": "Ninja",
|
||||||
"configurationType": "RelWithDebInfo",
|
"configurationType": "RelWithDebInfo",
|
||||||
"inheritEnvironments": [ "msvc_x64" ],
|
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||||
"buildRoot": "${env.BuildDir}\\${name}",
|
"buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
|
||||||
"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",
|
"installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}",
|
||||||
"buildCommandArgs": "",
|
"cmakeCommandArgs": "-DBUILD_TESTING=on -DDOKAN_LIB_PATH=\"C:\\Program Files\\Dokan\\DokanLibrary-1.1.0\"",
|
||||||
|
"buildCommandArgs": "-v",
|
||||||
"ctestCommandArgs": ""
|
"ctestCommandArgs": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -77,8 +77,6 @@ Building on Windows (experimental)
|
|||||||
Build with Visual Studio 2017 and pass in the following flags to CMake:
|
Build with Visual Studio 2017 and pass in the following flags to CMake:
|
||||||
|
|
||||||
-DDOKAN_LIB_PATH=[dokan library location, e.g. "C:\Program Files\Dokan\DokanLibrary-1.1.0"]
|
-DDOKAN_LIB_PATH=[dokan library location, e.g. "C:\Program Files\Dokan\DokanLibrary-1.1.0"]
|
||||||
-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"]
|
|
||||||
-DCURL_INCLUDE_DIR=[path to libcurl include files, e.g. "C:\ProgramData\chocolatey\lib\curl\tools\curl-7.61.1-win64-mingw\include"]
|
|
||||||
-DBOOST_ROOT=[path to root of boost installation]
|
-DBOOST_ROOT=[path to root of boost installation]
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
|
@ -27,14 +27,13 @@ init:
|
|||||||
- echo %generator%
|
- echo %generator%
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- choco install -y curl
|
|
||||||
- choco install -y dokany --installargs ADDLOCAL=DokanDevFeature,DokanLibBFeature,DokanPDBFeature
|
- choco install -y dokany --installargs ADDLOCAL=DokanDevFeature,DokanLibBFeature,DokanPDBFeature
|
||||||
- cmake --version
|
- cmake --version
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- cmd: mkdir build
|
- cmd: mkdir build
|
||||||
- cmd: cd build
|
- cmd: cd build
|
||||||
- cmake .. -G "%generator%" -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"
|
- cmake .. -G "%generator%" -DBUILD_TESTING=on -DBOOST_ROOT=C:\Libraries\boost_1_65_1 -DDOKAN_LIB_PATH="C:\Program Files\Dokan\DokanLibrary-1.1.0"
|
||||||
# TODO Make build parallel
|
# TODO Make build parallel
|
||||||
- cmd: cmake --build . --config %CONFIGURATION%
|
- cmd: cmake --build . --config %CONFIGURATION%
|
||||||
- cmd: .\test\gitversion\%CONFIGURATION%\gitversion-test.exe
|
- cmd: .\test\gitversion\%CONFIGURATION%\gitversion-test.exe
|
||||||
|
@ -15,7 +15,7 @@ set(SOURCES
|
|||||||
tempfile/TempDir.cpp
|
tempfile/TempDir.cpp
|
||||||
network/HttpClient.cpp
|
network/HttpClient.cpp
|
||||||
network/CurlHttpClient.cpp
|
network/CurlHttpClient.cpp
|
||||||
network/CurlInitializerRAII.cpp
|
network/WinHttpClient.cpp
|
||||||
network/FakeHttpClient.cpp
|
network/FakeHttpClient.cpp
|
||||||
io/Console.cpp
|
io/Console.cpp
|
||||||
io/DontEchoStdinToStdoutRAII.cpp
|
io/DontEchoStdinToStdoutRAII.cpp
|
||||||
@ -55,9 +55,6 @@ set(SOURCES
|
|||||||
|
|
||||||
add_library(${PROJECT_NAME} STATIC ${SOURCES})
|
add_library(${PROJECT_NAME} STATIC ${SOURCES})
|
||||||
|
|
||||||
find_package(CURL REQUIRED)
|
|
||||||
target_include_directories(${PROJECT_NAME} PUBLIC ${CURL_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${CURL_LIBRARIES})
|
|
||||||
|
|
||||||
if(NOT MSVC)
|
if(NOT MSVC)
|
||||||
find_package(Backtrace REQUIRED)
|
find_package(Backtrace REQUIRED)
|
||||||
@ -67,6 +64,14 @@ else()
|
|||||||
target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp)
|
target_link_libraries(${PROJECT_NAME} PUBLIC DbgHelp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (NOT MSVC)
|
||||||
|
find_package(CURL REQUIRED)
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC ${CURL_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC ${CURL_LIBRARIES})
|
||||||
|
else()
|
||||||
|
target_link_libraries(${PROJECT_NAME} PUBLIC WinHttp)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
|
target_link_libraries(${PROJECT_NAME} PUBLIC ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
|
||||||
|
@ -1,17 +1,39 @@
|
|||||||
// Base version taken from https://techoverflow.net/blog/2013/03/15/c-simple-http-download-using-libcurl-easy-api/
|
// Base version taken from https://techoverflow.net/blog/2013/03/15/c-simple-http-download-using-libcurl-easy-api/
|
||||||
|
|
||||||
|
#if !defined(_MSC_VER)
|
||||||
|
|
||||||
#include "CurlHttpClient.h"
|
#include "CurlHttpClient.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <curl/easy.h>
|
|
||||||
|
|
||||||
using boost::none;
|
using boost::none;
|
||||||
using boost::optional;
|
using boost::optional;
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::ostringstream;
|
using std::ostringstream;
|
||||||
|
using std::mutex;
|
||||||
|
using std::unique_lock;
|
||||||
|
|
||||||
namespace cpputils {
|
namespace cpputils {
|
||||||
|
|
||||||
|
mutex CurlHttpClient::CurlInitializerRAII::_mutex;
|
||||||
|
uint32_t CurlHttpClient::CurlInitializerRAII::_refcount = 0;
|
||||||
|
|
||||||
|
CurlHttpClient::CurlInitializerRAII::CurlInitializerRAII() {
|
||||||
|
unique_lock<mutex> lock(_mutex);
|
||||||
|
if (0 == _refcount) {
|
||||||
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
|
}
|
||||||
|
_refcount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurlHttpClient::CurlInitializerRAII::~CurlInitializerRAII() {
|
||||||
|
unique_lock<mutex> lock(_mutex);
|
||||||
|
_refcount -= 1;
|
||||||
|
if (0 == _refcount) {
|
||||||
|
curl_global_cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
size_t CurlHttpClient::write_data(void *ptr, size_t size, size_t nmemb, ostringstream *stream) {
|
size_t CurlHttpClient::write_data(void *ptr, size_t size, size_t nmemb, ostringstream *stream) {
|
||||||
stream->write(static_cast<const char *>(ptr), size * nmemb);
|
stream->write(static_cast<const char *>(ptr), size * nmemb);
|
||||||
return size * nmemb;
|
return size * nmemb;
|
||||||
@ -25,7 +47,7 @@ namespace cpputils {
|
|||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional <string> CurlHttpClient::get(const string &url, optional<long> timeoutMsec) {
|
string CurlHttpClient::get(const string &url, optional<long> timeoutMsec) {
|
||||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||||
// example.com is redirected, so we tell libcurl to follow redirection
|
// example.com is redirected, so we tell libcurl to follow redirection
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
@ -41,9 +63,11 @@ namespace cpputils {
|
|||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
// Check for errors
|
// Check for errors
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
return none;
|
throw std::runtime_error("Curl Error " + std::to_string(res) + ": " + curl_easy_strerror(res));
|
||||||
}
|
}
|
||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#ifndef MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_HPP
|
#ifndef MESSMER_CPPUTILS_NETWORK_CURLHTTPCLIENT_HPP
|
||||||
#define MESSMER_CPPUTILS_NETWORK_HTTPCLIENT_HPP
|
#define MESSMER_CPPUTILS_NETWORK_CURLHTTPCLIENT_HPP
|
||||||
|
|
||||||
|
#if !defined(_MSC_VER)
|
||||||
|
|
||||||
#include "HttpClient.h"
|
#include "HttpClient.h"
|
||||||
#include "../macros.h"
|
#include "../macros.h"
|
||||||
#include "CurlInitializerRAII.h"
|
#include <mutex>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
namespace cpputils {
|
namespace cpputils {
|
||||||
|
|
||||||
@ -14,9 +17,22 @@ namespace cpputils {
|
|||||||
|
|
||||||
~CurlHttpClient();
|
~CurlHttpClient();
|
||||||
|
|
||||||
boost::optional <std::string> get(const std::string &url, boost::optional<long> timeoutMsec = boost::none) override;
|
std::string get(const std::string &url, boost::optional<long> timeoutMsec = boost::none) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// When the first object of this class is created, it will initialize curl using curl_global_init().
|
||||||
|
// When the last object is destroyed, it will deinitialize curl using curl_global_cleanup().
|
||||||
|
class CurlInitializerRAII final {
|
||||||
|
public:
|
||||||
|
CurlInitializerRAII();
|
||||||
|
~CurlInitializerRAII();
|
||||||
|
private:
|
||||||
|
static std::mutex _mutex;
|
||||||
|
static uint32_t _refcount;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(CurlInitializerRAII);
|
||||||
|
};
|
||||||
|
|
||||||
CurlInitializerRAII curlInitializer;
|
CurlInitializerRAII curlInitializer;
|
||||||
CURL *curl;
|
CURL *curl;
|
||||||
|
|
||||||
@ -28,3 +44,5 @@ namespace cpputils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
#include "CurlInitializerRAII.h"
|
|
||||||
|
|
||||||
using std::mutex;
|
|
||||||
using std::unique_lock;
|
|
||||||
|
|
||||||
namespace cpputils {
|
|
||||||
|
|
||||||
mutex CurlInitializerRAII::_mutex;
|
|
||||||
uint32_t CurlInitializerRAII::_refcount = 0;
|
|
||||||
|
|
||||||
CurlInitializerRAII::CurlInitializerRAII() {
|
|
||||||
unique_lock<mutex> lock(_mutex);
|
|
||||||
if (0 == _refcount) {
|
|
||||||
curl_global_init(CURL_GLOBAL_ALL);
|
|
||||||
}
|
|
||||||
_refcount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
CurlInitializerRAII::~CurlInitializerRAII() {
|
|
||||||
unique_lock<mutex> lock(_mutex);
|
|
||||||
_refcount -= 1;
|
|
||||||
if (0 == _refcount) {
|
|
||||||
curl_global_cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#ifndef MESSMER_CPPUTILS_NETWORK_CURLINITIALIZERRAII_HPP
|
|
||||||
#define MESSMER_CPPUTILS_NETWORK_CURLINITIALIZERRAII_HPP
|
|
||||||
|
|
||||||
#include <cpp-utils/macros.h>
|
|
||||||
#include <mutex>
|
|
||||||
#include <curl/curl.h>
|
|
||||||
|
|
||||||
namespace cpputils {
|
|
||||||
// TODO Test
|
|
||||||
|
|
||||||
// When the first object of this class is created, it will initialize curl using curl_global_init().
|
|
||||||
// When the last object is destroyed, it will deinitialize curl using curl_global_cleanup().
|
|
||||||
class CurlInitializerRAII final {
|
|
||||||
public:
|
|
||||||
CurlInitializerRAII();
|
|
||||||
~CurlInitializerRAII();
|
|
||||||
private:
|
|
||||||
static std::mutex _mutex;
|
|
||||||
static uint32_t _refcount;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(CurlInitializerRAII);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -12,11 +12,11 @@ namespace cpputils {
|
|||||||
_sites[url] = content;
|
_sites[url] = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<string> FakeHttpClient::get(const string &url, optional<long> timeoutMsec) {
|
string FakeHttpClient::get(const string &url, optional<long> timeoutMsec) {
|
||||||
UNUSED(timeoutMsec);
|
UNUSED(timeoutMsec);
|
||||||
auto found = _sites.find(url);
|
auto found = _sites.find(url);
|
||||||
if (found == _sites.end()) {
|
if (found == _sites.end()) {
|
||||||
return none;
|
throw std::runtime_error("Website doesn't exist in FakeHttpClient.");
|
||||||
}
|
}
|
||||||
return found->second;
|
return found->second;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ namespace cpputils {
|
|||||||
|
|
||||||
void addWebsite(const std::string &url, const std::string &content);
|
void addWebsite(const std::string &url, const std::string &content);
|
||||||
|
|
||||||
boost::optional<std::string> get(const std::string &url, boost::optional<long> timeoutMsec = boost::none) override;
|
std::string get(const std::string &url, boost::optional<long> timeoutMsec = boost::none) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<std::string, std::string> _sites;
|
std::map<std::string, std::string> _sites;
|
||||||
|
@ -9,7 +9,7 @@ namespace cpputils {
|
|||||||
public:
|
public:
|
||||||
virtual ~HttpClient() {}
|
virtual ~HttpClient() {}
|
||||||
|
|
||||||
virtual boost::optional<std::string> get(const std::string& url, boost::optional<long> timeoutMsec = boost::none) = 0;
|
virtual std::string get(const std::string& url, boost::optional<long> timeoutMsec = boost::none) = 0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
258
src/cpp-utils/network/WinHttpClient.cpp
Normal file
258
src/cpp-utils/network/WinHttpClient.cpp
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
#if defined(_MSC_VER)
|
||||||
|
|
||||||
|
#include "WinHttpClient.h"
|
||||||
|
#include <sstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <cpp-utils/assert/assert.h>
|
||||||
|
#include <cpp-utils/data/Data.h>
|
||||||
|
#include <codecvt>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <Winhttp.h>
|
||||||
|
|
||||||
|
using boost::none;
|
||||||
|
using boost::optional;
|
||||||
|
using std::string;
|
||||||
|
using std::wstring;
|
||||||
|
using std::wstring_convert;
|
||||||
|
using std::ostringstream;
|
||||||
|
|
||||||
|
namespace cpputils {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct HttpHandleRAII final {
|
||||||
|
HINTERNET handle;
|
||||||
|
|
||||||
|
HttpHandleRAII(HINTERNET handle_) : handle(handle_) {}
|
||||||
|
|
||||||
|
HttpHandleRAII(HttpHandleRAII&& rhs) : handle(rhs.handle) {
|
||||||
|
rhs.handle = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~HttpHandleRAII() {
|
||||||
|
if (nullptr != handle) {
|
||||||
|
BOOL success = WinHttpCloseHandle(handle);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpCloseHandle. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(HttpHandleRAII);
|
||||||
|
};
|
||||||
|
|
||||||
|
URL_COMPONENTS parse_url(const wstring &url) {
|
||||||
|
URL_COMPONENTS result;
|
||||||
|
result.dwStructSize = sizeof(result);
|
||||||
|
// Declare fields we want. Setting a field to nullptr and the length to non-zero means the field will be returned.
|
||||||
|
result.lpszScheme = nullptr;
|
||||||
|
result.dwSchemeLength = 1;
|
||||||
|
result.lpszHostName = nullptr;
|
||||||
|
result.dwHostNameLength = 1;
|
||||||
|
result.lpszUserName = nullptr;
|
||||||
|
result.dwUserNameLength = 1;
|
||||||
|
result.lpszPassword = nullptr;
|
||||||
|
result.dwPasswordLength = 1;
|
||||||
|
result.lpszUrlPath = nullptr;
|
||||||
|
result.dwUrlPathLength = 1;
|
||||||
|
result.lpszExtraInfo = nullptr;
|
||||||
|
result.dwExtraInfoLength = 1;
|
||||||
|
|
||||||
|
BOOL success = WinHttpCrackUrl(url.c_str(), url.size(), ICU_REJECT_USERPWD, &result);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error parsing url '" + wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(url) + "'. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
INTERNET_PORT get_port_from_url(const URL_COMPONENTS& parsedUrl) {
|
||||||
|
wstring scheme_str(parsedUrl.lpszScheme, parsedUrl.dwSchemeLength);
|
||||||
|
string s_(wstring_convert < std::codecvt_utf8_utf16<wchar_t>>().to_bytes(scheme_str));
|
||||||
|
if (parsedUrl.nScheme == INTERNET_SCHEME_HTTP) {
|
||||||
|
ASSERT(scheme_str == L"http", "Scheme mismatch");
|
||||||
|
if (parsedUrl.nPort != 80) {
|
||||||
|
throw std::runtime_error("We don't support non-default ports");
|
||||||
|
}
|
||||||
|
return INTERNET_DEFAULT_HTTP_PORT;
|
||||||
|
}
|
||||||
|
else if (parsedUrl.nScheme == INTERNET_SCHEME_HTTPS) {
|
||||||
|
ASSERT(scheme_str == L"https", "Scheme mismatch");
|
||||||
|
if (parsedUrl.nPort != 443) {
|
||||||
|
throw std::runtime_error("We don't support non-default ports");
|
||||||
|
}
|
||||||
|
return INTERNET_DEFAULT_HTTPS_PORT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw std::runtime_error("Unsupported scheme: " + wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(scheme_str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Request final {
|
||||||
|
public:
|
||||||
|
Request(HttpHandleRAII request) : request_(std::move(request)) {}
|
||||||
|
|
||||||
|
void set_redirect_policy(DWORD redirectPolicy) {
|
||||||
|
BOOL success = WinHttpSetOption(request_.handle, WINHTTP_OPTION_REDIRECT_POLICY, &redirectPolicy, sizeof(redirectPolicy));
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpSetOption. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_timeouts(long timeoutMsec) {
|
||||||
|
// TODO Timeout should be a total timeout, not per step as we're doing it here.
|
||||||
|
BOOL success = WinHttpSetTimeouts(request_.handle, timeoutMsec, timeoutMsec, timeoutMsec, timeoutMsec);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpSetTimeouts. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void send() {
|
||||||
|
BOOL success = WinHttpSendRequest(request_.handle, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpSendRequest. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wait_for_response() {
|
||||||
|
BOOL success = WinHttpReceiveResponse(request_.handle, nullptr);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpReceiveResponse. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD get_status_code() {
|
||||||
|
DWORD statusCode;
|
||||||
|
DWORD statusCodeSize = sizeof(statusCode);
|
||||||
|
BOOL success = WinHttpQueryHeaders(request_.handle, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Eror calling WinHttpQueryHeaders. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
string read_response() {
|
||||||
|
ostringstream result;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
DWORD size = num_bytes_readable();
|
||||||
|
if (size == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cpputils::Data buffer(size + 1);
|
||||||
|
buffer.FillWithZeroes();
|
||||||
|
|
||||||
|
DWORD num_read;
|
||||||
|
BOOL success = WinHttpReadData(request_.handle, buffer.data(), buffer.size(), &num_read);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpReadData. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
ASSERT(0 != num_read, "Weird behavior of WinHttpReadData.It should never read zero bytes since WinHttpQueryDataAvailable said there are bytes readable.");
|
||||||
|
|
||||||
|
result.write(reinterpret_cast<char*>(buffer.data()), num_read);
|
||||||
|
ASSERT(result.good(), "Error writing to ostringstream");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DWORD num_bytes_readable() {
|
||||||
|
DWORD result;
|
||||||
|
BOOL success = WinHttpQueryDataAvailable(request_.handle, &result);
|
||||||
|
if (!success) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpQueryDataAvailable. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHandleRAII request_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Connection final {
|
||||||
|
public:
|
||||||
|
Connection(HttpHandleRAII connection) : connection_(std::move(connection)) {}
|
||||||
|
|
||||||
|
Request create_request(const URL_COMPONENTS& parsedUrl) {
|
||||||
|
const INTERNET_PORT port = get_port_from_url(parsedUrl);
|
||||||
|
const wstring path = wstring(parsedUrl.lpszUrlPath, parsedUrl.dwUrlPathLength) + wstring(parsedUrl.lpszExtraInfo, parsedUrl.dwExtraInfoLength);
|
||||||
|
const DWORD flags = (port == INTERNET_DEFAULT_HTTPS_PORT) ? WINHTTP_FLAG_SECURE : 0;
|
||||||
|
|
||||||
|
HttpHandleRAII request_handle(WinHttpOpenRequest(connection_.handle, L"GET", path.c_str(), nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, flags));
|
||||||
|
if (nullptr == request_handle.handle) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpOpenRequest. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
return Request(std::move(request_handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HttpHandleRAII connection_;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WinHttpSession final {
|
||||||
|
public:
|
||||||
|
WinHttpSession(HttpHandleRAII session) : session_(std::move(session)) {}
|
||||||
|
|
||||||
|
Connection create_connection(const URL_COMPONENTS& parsedUrl) {
|
||||||
|
const INTERNET_PORT port = get_port_from_url(parsedUrl);
|
||||||
|
const wstring host(parsedUrl.lpszHostName, parsedUrl.dwHostNameLength);
|
||||||
|
|
||||||
|
HttpHandleRAII connection_handle = WinHttpConnect(session_.handle, host.c_str(), port, 0);
|
||||||
|
if (nullptr == connection_handle.handle) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpConnect. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Connection(std::move(connection_handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HttpHandleRAII session_;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::unique_ptr<WinHttpSession> create_session() {
|
||||||
|
HttpHandleRAII session_handle = WinHttpOpen(L"cpputils::HttpClient", WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||||
|
if(nullptr == session_handle.handle) {
|
||||||
|
throw std::runtime_error("Error calling WinHttpOpen. Error code: " + std::to_string(GetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<WinHttpSession>(std::move(session_handle));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WinHttpClient::WinHttpClient() : session_(create_session()) {}
|
||||||
|
|
||||||
|
WinHttpClient::~WinHttpClient() {}
|
||||||
|
|
||||||
|
string WinHttpClient::get(const string &url, optional<long> timeoutMsec) {
|
||||||
|
wstring wurl = wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(url);
|
||||||
|
const URL_COMPONENTS parsedUrl = parse_url(wurl);
|
||||||
|
|
||||||
|
ASSERT(parsedUrl.dwUserNameLength == 0, "Authentication not supported");
|
||||||
|
ASSERT(parsedUrl.dwPasswordLength == 0, "Authentication not supported");
|
||||||
|
|
||||||
|
Connection connection = session_->create_connection(parsedUrl);
|
||||||
|
Request request = connection.create_request(parsedUrl);
|
||||||
|
|
||||||
|
// allow redirects but not from https to http
|
||||||
|
request.set_redirect_policy(WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP);
|
||||||
|
|
||||||
|
if (timeoutMsec != none) {
|
||||||
|
request.set_timeouts(*timeoutMsec);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.send();
|
||||||
|
request.wait_for_response();
|
||||||
|
|
||||||
|
DWORD statusCode = request.get_status_code();
|
||||||
|
if (statusCode != HTTP_STATUS_OK) {
|
||||||
|
throw std::runtime_error("HTTP Server returned unsupported status code: " + std::to_string(statusCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.read_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
30
src/cpp-utils/network/WinHttpClient.h
Normal file
30
src/cpp-utils/network/WinHttpClient.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef MESSMER_CPPUTILS_NETWORK_WINHTTPCLIENT_HPP
|
||||||
|
#define MESSMER_CPPUTILS_NETWORK_WINHTTPCLIENT_HPP
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
|
||||||
|
#include "HttpClient.h"
|
||||||
|
#include "../macros.h"
|
||||||
|
|
||||||
|
namespace cpputils {
|
||||||
|
|
||||||
|
class WinHttpSession;
|
||||||
|
|
||||||
|
class WinHttpClient final : public HttpClient {
|
||||||
|
public:
|
||||||
|
WinHttpClient();
|
||||||
|
~WinHttpClient();
|
||||||
|
|
||||||
|
std::string get(const std::string &url, boost::optional<long> timeoutMsec = boost::none) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<WinHttpSession> session_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(WinHttpClient);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
@ -16,8 +16,7 @@ using namespace cpputils::logging;
|
|||||||
namespace cryfs {
|
namespace cryfs {
|
||||||
|
|
||||||
VersionChecker::VersionChecker(HttpClient* httpClient)
|
VersionChecker::VersionChecker(HttpClient* httpClient)
|
||||||
: _versionInfo(_getVersionInfo(httpClient)) {
|
: _versionInfo(_getVersionInfo(httpClient)) {}
|
||||||
}
|
|
||||||
|
|
||||||
optional<string> VersionChecker::newestVersion() const {
|
optional<string> VersionChecker::newestVersion() const {
|
||||||
if (_versionInfo == none) {
|
if (_versionInfo == none) {
|
||||||
@ -48,11 +47,15 @@ namespace cryfs {
|
|||||||
|
|
||||||
optional<ptree> VersionChecker::_getVersionInfo(HttpClient* httpClient) {
|
optional<ptree> VersionChecker::_getVersionInfo(HttpClient* httpClient) {
|
||||||
long timeoutMsec = 2000;
|
long timeoutMsec = 2000;
|
||||||
optional<string> response = httpClient->get("https://www.cryfs.org/version_info.json", timeoutMsec);
|
string response;
|
||||||
if (response == none) {
|
try {
|
||||||
return none;
|
response = httpClient->get("https://www.cryfs.org/version_info.json", timeoutMsec);
|
||||||
}
|
}
|
||||||
return _parseJson(*response);
|
catch (const std::exception& e) {
|
||||||
|
LOG(WARN, "HTTP Error: {}", e.what());
|
||||||
|
return none;
|
||||||
|
}
|
||||||
|
return _parseJson(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<ptree> VersionChecker::_parseJson(const string &json) {
|
optional<ptree> VersionChecker::_parseJson(const string &json) {
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
#include "Cli.h"
|
#include "Cli.h"
|
||||||
#include <cpp-utils/random/Random.h>
|
#include <cpp-utils/random/Random.h>
|
||||||
#include <cpp-utils/crypto/kdf/Scrypt.h>
|
#include <cpp-utils/crypto/kdf/Scrypt.h>
|
||||||
#include <cpp-utils/network/CurlHttpClient.h>
|
|
||||||
#include <cpp-utils/io/IOStreamConsole.h>
|
#include <cpp-utils/io/IOStreamConsole.h>
|
||||||
#include <cryfs/CryfsException.h>
|
#include <cryfs/CryfsException.h>
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#include <cpp-utils/network/WinHttpClient.h>
|
||||||
|
#else
|
||||||
|
#include <cpp-utils/network/CurlHttpClient.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace cryfs;
|
using namespace cryfs;
|
||||||
using cpputils::Random;
|
using cpputils::Random;
|
||||||
using cpputils::SCrypt;
|
using cpputils::SCrypt;
|
||||||
using cpputils::CurlHttpClient;
|
|
||||||
using cpputils::IOStreamConsole;
|
using cpputils::IOStreamConsole;
|
||||||
using cpputils::make_unique_ref;
|
using cpputils::make_unique_ref;
|
||||||
using std::make_shared;
|
using std::make_shared;
|
||||||
@ -17,8 +21,13 @@ using std::cerr;
|
|||||||
int main(int argc, const char *argv[]) {
|
int main(int argc, const char *argv[]) {
|
||||||
try {
|
try {
|
||||||
auto &keyGenerator = Random::OSRandom();
|
auto &keyGenerator = Random::OSRandom();
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
auto httpClient = make_unique_ref<cpputils::WinHttpClient>();
|
||||||
|
#else
|
||||||
|
auto httpClient = make_unique_ref<cpputils::CurlHttpClient>();
|
||||||
|
#endif
|
||||||
return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared<IOStreamConsole>())
|
return Cli(keyGenerator, SCrypt::DefaultSettings, make_shared<IOStreamConsole>())
|
||||||
.main(argc, argv, make_unique_ref<CurlHttpClient>());
|
.main(argc, argv, std::move(httpClient));
|
||||||
} catch (const CryfsException &e) {
|
} catch (const CryfsException &e) {
|
||||||
if (e.errorCode() != ErrorCode::Success) {
|
if (e.errorCode() != ErrorCode::Success) {
|
||||||
std::cerr << "Error: " << e.what() << std::endl;
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
|
@ -7,33 +7,33 @@ using boost::none;
|
|||||||
using namespace cpputils;
|
using namespace cpputils;
|
||||||
|
|
||||||
TEST(FakeHttpClientTest, Empty) {
|
TEST(FakeHttpClientTest, Empty) {
|
||||||
EXPECT_EQ(none, FakeHttpClient().get("http://example.com"));
|
EXPECT_ANY_THROW(FakeHttpClient().get("http://example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FakeHttpClientTest, Nonexisting) {
|
TEST(FakeHttpClientTest, Nonexisting) {
|
||||||
FakeHttpClient client;
|
FakeHttpClient client;
|
||||||
client.addWebsite("http://existing.com", "content");
|
client.addWebsite("http://existing.com", "content");
|
||||||
EXPECT_EQ(none, client.get("http://notexisting.com"));
|
EXPECT_ANY_THROW(client.get("http://notexisting.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FakeHttpClientTest, Existing) {
|
TEST(FakeHttpClientTest, Existing) {
|
||||||
FakeHttpClient client;
|
FakeHttpClient client;
|
||||||
client.addWebsite("http://existing.com", "content");
|
client.addWebsite("http://existing.com", "content");
|
||||||
EXPECT_EQ("content", client.get("http://existing.com").value());
|
EXPECT_EQ("content", client.get("http://existing.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FakeHttpClientTest, TwoExisting) {
|
TEST(FakeHttpClientTest, TwoExisting) {
|
||||||
FakeHttpClient client;
|
FakeHttpClient client;
|
||||||
client.addWebsite("http://firstexisting.com", "first_content");
|
client.addWebsite("http://firstexisting.com", "first_content");
|
||||||
client.addWebsite("http://secondexisting.com", "second_content");
|
client.addWebsite("http://secondexisting.com", "second_content");
|
||||||
EXPECT_EQ("first_content", client.get("http://firstexisting.com").value());
|
EXPECT_EQ("first_content", client.get("http://firstexisting.com"));
|
||||||
EXPECT_EQ("second_content", client.get("http://secondexisting.com").value());
|
EXPECT_EQ("second_content", client.get("http://secondexisting.com"));
|
||||||
EXPECT_EQ(none, client.get("http://notexisting.com"));
|
EXPECT_ANY_THROW(client.get("http://notexisting.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(FakeHttpClientTest, Overwriting) {
|
TEST(FakeHttpClientTest, Overwriting) {
|
||||||
FakeHttpClient client;
|
FakeHttpClient client;
|
||||||
client.addWebsite("http://existing.com", "content");
|
client.addWebsite("http://existing.com", "content");
|
||||||
client.addWebsite("http://existing.com", "new_content");
|
client.addWebsite("http://existing.com", "new_content");
|
||||||
EXPECT_EQ("new_content", client.get("http://existing.com").value());
|
EXPECT_EQ("new_content", client.get("http://existing.com"));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user