6#include <asio/connect.hpp>
7#include <asio/ip/tcp.hpp>
8#include <asio/read.hpp>
9#include <asio/read_until.hpp>
10#include <asio/ssl.hpp>
11#include <asio/streambuf.hpp>
12#include <openssl/x509.h>
25#undef PKCS7_ISSUER_AND_SERIAL
26#undef PKCS7_SIGNER_INFO
29#elif defined(__APPLE__)
30#include <Security/Security.h>
31#include <CoreFoundation/CoreFoundation.h>
39 void AddDERCert(X509_STORE* store,
const unsigned char* data,
const long len)
41 X509* x509 = d2i_X509(
nullptr, &data, len);
44 X509_STORE_add_cert(store, x509);
49 void LoadSystemCertificates(asio::ssl::context& ctx)
51 ctx.set_default_verify_paths();
54 X509_STORE* store = SSL_CTX_get_cert_store(ctx.native_handle());
55 for (
const char* name : {
"ROOT",
"CA" })
57 HCERTSTORE hStore = CertOpenSystemStoreA(0, name);
62 PCCERT_CONTEXT cert =
nullptr;
63 while ((cert = CertEnumCertificatesInStore(hStore, cert)) !=
nullptr)
65 AddDERCert(store, cert->pbCertEncoded, cert->cbCertEncoded);
68 CertCloseStore(hStore, 0);
70#elif defined __APPLE__
71 X509_STORE* store = SSL_CTX_get_cert_store(ctx.native_handle());
72 CFArrayRef certs =
nullptr;
73 if (SecTrustCopyAnchorCertificates(&certs) == errSecSuccess && certs)
75 for (CFIndex i = 0; i < CFArrayGetCount(certs); ++i)
77 auto cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, i);
78 CFDataRef der = SecCertificateCopyData(cert);
81 AddDERCert(store, CFDataGetBytePtr(der), CFDataGetLength(der));
88 for (
const char* path : {
"/etc/ssl/certs/ca-certificates.crt",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem",
"/etc/ssl/cert.pem" })
91 ctx.load_verify_file(path, ec);
96 WebRequestResponse WebRequest(
97 const std::string& host,
98 const std::string& raw_request
101 asio::io_context io_context;
104 asio::ip::tcp::resolver resolver(io_context);
105 asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host,
"https");
108 static asio::ssl::context ctx = []() {
109 asio::ssl::context c(asio::ssl::context::tls_client);
110 c.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::no_sslv3);
111 LoadSystemCertificates(c);
115 asio::ssl::stream<asio::ip::tcp::socket> socket(io_context, ctx);
116 socket.set_verify_mode(asio::ssl::verify_none);
117 socket.set_verify_callback(asio::ssl::host_name_verification(host));
118 SSL_set_tlsext_host_name(socket.native_handle(), host.c_str());
119 asio::connect(socket.lowest_layer(), endpoints);
120 asio::error_code ec{};
121 socket.handshake(socket.client, ec);
124 LOG_ERROR(
"Error while performing Web SSL handshake: " << ec <<
"(" << ec.message() <<
")");
125 return WebRequestResponse{
131 socket.lowest_layer().set_option(asio::ip::tcp::no_delay(
true));
134 asio::streambuf request;
135 std::ostream request_stream(&request);
136 request_stream << raw_request;
138 asio::write(socket, request);
140 WebRequestResponse web_response;
145 asio::streambuf response;
146 asio::read_until(socket, response,
"\r\n");
149 std::istream response_stream(&response);
150 std::string http_version;
151 response_stream >> http_version;
152 response_stream >> web_response.status_code;
153 std::getline(response_stream, web_response.status_message);
156 web_response.status_message.erase(std::remove(web_response.status_message.begin(), web_response.status_message.end(),
'\r'),
157 web_response.status_message.end());
159 if (!response_stream || http_version.substr(0, 5) !=
"HTTP/")
161 LOG_ERROR(
"Invalid response during web request");
162 web_response.response = {};
167 if (web_response.status_code == 204)
169 web_response.response = {};
174 asio::read_until(socket, response,
"\r\n\r\n");
177 long long int data_length = -1;
178 bool chunked =
false;
180 while (std::getline(response_stream, header) && header !=
"\r")
183 if (!header.empty() && header.back() ==
'\r')
188 if (header.find(
"Content-Length: ") == 0)
190 data_length = std::stoll(header.substr(16));
192 else if (header.find(
"Transfer-Encoding:") == 0 && header.find(
"chunked") != std::string::npos)
199 std::stringstream output_stringstream;
200 if (response.size() > 0)
202 output_stringstream << &response;
206 asio::error_code error;
207 while (asio::read(socket, response, asio::transfer_at_least(1), error))
209 output_stringstream << &response;
211 const std::string raw_body = output_stringstream.str();
213 if (error != asio::error::eof && (data_length < 0 || raw_body.size() !=
static_cast<size_t>(data_length)))
215 LOG_ERROR(
"Error trying to read web request response, Error:\n " << error);
216 web_response.response = {};
222 std::string decoded_body;
223 bool decode_ok =
false;
226 while (pos < raw_body.size())
228 const size_t line_end = raw_body.find(
"\r\n", pos);
229 if (line_end == std::string::npos)
235 std::string size_line = raw_body.substr(pos, line_end - pos);
236 const size_t semicolon = size_line.find(
";");
237 if (semicolon != std::string::npos)
239 size_line.resize(semicolon);
242 const size_t chunk_size = std::stoull(size_line,
nullptr, 16);
251 if (pos + chunk_size > raw_body.size())
256 decoded_body.append(raw_body, pos, chunk_size);
258 pos += chunk_size + 2;
263 LOG_ERROR(
"Malformed chunked web request response");
264 web_response.response = {};
280 const std::string& host,
const std::string& endpoint,
281 const std::string& content_type,
const std::string& accept,
282 const std::string& authorization,
const std::string& data
288 std::string raw_request =
"";
289 raw_request +=
"POST " + endpoint +
" HTTP/1.1 \r\n";
290 raw_request +=
"Host: " + host +
"\r\n";
291 raw_request +=
"User-Agent: C/1.0\r\n";
292 raw_request +=
"Content-Type: " + content_type +
" \r\n";
293 raw_request +=
"Accept: " + accept +
"\r\n";
294 if (!authorization.empty())
296 raw_request +=
"Authorization: " + authorization +
"\r\n";
298 raw_request +=
"Content-Length: " + std::to_string(data.length()) +
"\r\n";
299 raw_request +=
"Connection: close\r\n\r\n";
302 return WebRequest(host, raw_request);
306 const std::string& host,
307 const std::string& endpoint,
308 const std::string& authorization)
313 std::string raw_request =
"";
314 raw_request +=
"GET " + endpoint +
" HTTP/1.1 \r\n";
315 raw_request +=
"Host: " + host +
"\r\n";
316 if (!authorization.empty())
318 raw_request +=
"Authorization: " + authorization +
"\r\n";
320 raw_request +=
"User-Agent: C/1.0\r\n";
321 raw_request +=
"Connection: close\r\n\r\n";
323 return WebRequest(host, raw_request);
328 const std::string& s,
329 const std::vector<std::string>& fields,
330 const unsigned int expected_status
337 for (
const std::string& f : fields)
341 throw std::runtime_error(
"No " + f +
" in " + s +
" response");
#define LOG_ERROR(osstream)
Main class, basically a JsonVariant with extra utility functions it doesn't inherit JsonVariant direc...
std::string Dump(const int indent=-1, const char indent_char=' ') const
public dump interface
bool contains(const std::string &s) const
WebRequestResponse GETRequest(const std::string &host, const std::string &endpoint, const std::string &authorization="")
Send a GET request with ssl stuff.
ProtocolCraft::Json::Value CheckJsonResponse(const WebRequestResponse &response, const std::string &s, const std::vector< std::string > &fields, const unsigned int expected_status=200)
Check that a WebRequestResponse has the right fields and return code, throw if not.
WebRequestResponse POSTRequest(const std::string &host, const std::string &endpoint, const std::string &content_type, const std::string &accept, const std::string &authorization, const std::string &data)
Send a POST request with ssl stuff.
Value Parse(std::string_view::const_iterator iter, size_t length, bool no_except=false)
Parse a string_view from iter for at most length characters.
ProtocolCraft::Json::Value response
std::string status_message