3
* (C) 2013,2016 Jack Lloyd
4
* 2017 René Korthaus, Rohde & Schwarz Cybersecurity
6
* Botan is released under the Simplified BSD License (see license.txt)
9
#include <botan/http_util.h>
10
#include <botan/parsing.h>
11
#include <botan/hex.h>
12
#include <botan/internal/os_utils.h>
13
#include <botan/internal/socket.h>
14
#include <botan/internal/stl_util.h>
24
* Connect to a host, write some bytes, then read until the server
27
std::string http_transact(const std::string& hostname,
28
const std::string& message,
29
std::chrono::milliseconds timeout)
31
std::unique_ptr<OS::Socket> socket;
33
const std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now();
37
socket = OS::open_socket(hostname, "http", timeout);
39
throw Exception("No socket support enabled in build");
41
catch(std::exception& e)
43
throw HTTP_Error("HTTP connection to " + hostname + " failed: " + e.what());
46
// Blocks until entire message has been written
47
socket->write(cast_char_ptr_to_uint8(message.data()),
50
if(std::chrono::system_clock::now() - start_time > timeout)
51
throw HTTP_Error("Timeout during writing message body");
53
std::ostringstream oss;
54
std::vector<uint8_t> buf(BOTAN_DEFAULT_BUFFER_SIZE);
57
const size_t got = socket->read(buf.data(), buf.size());
61
if(std::chrono::system_clock::now() - start_time > timeout)
62
throw HTTP_Error("Timeout while reading message body");
64
oss.write(cast_uint8_ptr_to_char(buf.data()),
65
static_cast<std::streamsize>(got));
73
std::string url_encode(const std::string& in)
75
std::ostringstream out;
79
if(c >= 'A' && c <= 'Z')
81
else if(c >= 'a' && c <= 'z')
83
else if(c >= '0' && c <= '9')
85
else if(c == '-' || c == '_' || c == '.' || c == '~')
88
out << '%' << hex_encode(cast_char_ptr_to_uint8(&c), 1);
94
std::ostream& operator<<(std::ostream& o, const Response& resp)
96
o << "HTTP " << resp.status_code() << " " << resp.status_message() << "\n";
97
for(auto h : resp.headers())
98
o << "Header '" << h.first << "' = '" << h.second << "'\n";
99
o << "Body " << std::to_string(resp.body().size()) << " bytes:\n";
100
o.write(cast_uint8_ptr_to_char(resp.body().data()), resp.body().size());
104
Response http_sync(http_exch_fn http_transact,
105
const std::string& verb,
106
const std::string& url,
107
const std::string& content_type,
108
const std::vector<uint8_t>& body,
109
size_t allowable_redirects)
112
throw HTTP_Error("URL empty");
114
const auto protocol_host_sep = url.find("://");
115
if(protocol_host_sep == std::string::npos)
116
throw HTTP_Error("Invalid URL '" + url + "'");
118
const auto host_loc_sep = url.find('/', protocol_host_sep + 3);
120
std::string hostname, loc;
122
if(host_loc_sep == std::string::npos)
124
hostname = url.substr(protocol_host_sep + 3, std::string::npos);
129
hostname = url.substr(protocol_host_sep + 3, host_loc_sep-protocol_host_sep-3);
130
loc = url.substr(host_loc_sep, std::string::npos);
133
std::ostringstream outbuf;
135
outbuf << verb << " " << loc << " HTTP/1.0\r\n";
136
outbuf << "Host: " << hostname << "\r\n";
140
outbuf << "Accept: */*\r\n";
141
outbuf << "Cache-Control: no-cache\r\n";
143
else if(verb == "POST")
144
outbuf << "Content-Length: " << body.size() << "\r\n";
146
if(!content_type.empty())
147
outbuf << "Content-Type: " << content_type << "\r\n";
148
outbuf << "Connection: close\r\n\r\n";
149
outbuf.write(cast_uint8_ptr_to_char(body.data()), body.size());
151
std::istringstream io(http_transact(hostname, outbuf.str()));
154
std::getline(io, line1);
155
if(!io || line1.empty())
156
throw HTTP_Error("No response");
158
std::stringstream response_stream(line1);
159
std::string http_version;
160
unsigned int status_code;
161
std::string status_message;
163
response_stream >> http_version >> status_code;
165
std::getline(response_stream, status_message);
167
if(!response_stream || http_version.substr(0,5) != "HTTP/")
168
throw HTTP_Error("Not an HTTP response");
170
std::map<std::string, std::string> headers;
171
std::string header_line;
172
while (std::getline(io, header_line) && header_line != "\r")
174
auto sep = header_line.find(": ");
175
if(sep == std::string::npos || sep > header_line.size() - 2)
176
throw HTTP_Error("Invalid HTTP header " + header_line);
177
const std::string key = header_line.substr(0, sep);
179
if(sep + 2 < header_line.size() - 1)
181
const std::string val = header_line.substr(sep + 2, (header_line.size() - 1) - (sep + 2));
186
if(status_code == 301 && headers.count("Location"))
188
if(allowable_redirects == 0)
189
throw HTTP_Error("HTTP redirection count exceeded");
190
return GET_sync(headers["Location"], allowable_redirects - 1);
193
std::vector<uint8_t> resp_body;
194
std::vector<uint8_t> buf(4096);
197
io.read(cast_uint8_ptr_to_char(buf.data()), buf.size());
198
resp_body.insert(resp_body.end(), buf.data(), &buf[io.gcount()]);
201
const std::string header_size = search_map(headers, std::string("Content-Length"));
203
if(!header_size.empty())
205
if(resp_body.size() != to_u32bit(header_size))
206
throw HTTP_Error("Content-Length disagreement, header says " +
207
header_size + " got " + std::to_string(resp_body.size()));
210
return Response(status_code, status_message, resp_body, headers);
213
Response http_sync(const std::string& verb,
214
const std::string& url,
215
const std::string& content_type,
216
const std::vector<uint8_t>& body,
217
size_t allowable_redirects,
218
std::chrono::milliseconds timeout)
220
auto transact_with_timeout =
221
[timeout](const std::string& hostname, const std::string& service)
223
return http_transact(hostname, service, timeout);
227
transact_with_timeout,
232
allowable_redirects);
235
Response GET_sync(const std::string& url,
236
size_t allowable_redirects,
237
std::chrono::milliseconds timeout)
239
return http_sync("GET", url, "", std::vector<uint8_t>(), allowable_redirects, timeout);
242
Response POST_sync(const std::string& url,
243
const std::string& content_type,
244
const std::vector<uint8_t>& body,
245
size_t allowable_redirects,
246
std::chrono::milliseconds timeout)
248
return http_sync("POST", url, content_type, body, allowable_redirects, timeout);