Botan 2.1.0 Crypto and TLS for C++11

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

http_util.cpp
Go to the documentation of this file.
1 /*
2 * Sketchy HTTP client
3 * (C) 2013,2016 Jack Lloyd
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7
8 #include <botan/http_util.h>
9 #include <botan/parsing.h>
10 #include <botan/hex.h>
11 #include <botan/internal/stl_util.h>
12 #include <sstream>
13
14 #if defined(BOTAN_HAS_BOOST_ASIO)
15
16 /*
17 * We don't need serial port support anyway, and asking for it
18 * causes macro conflicts with Darwin's termios.h when this
19 * file is included in the amalgamation. GH #350
20 */
21 #define BOOST_ASIO_DISABLE_SERIAL_PORT
22 #include <boost/asio.hpp>
23
24 #elif defined(BOTAN_TARGET_OS_HAS_SOCKETS)
25 #include <sys/types.h>
26 #include <sys/socket.h>
27 #include <netdb.h>
28 #include <unistd.h>
29 #include <netinet/in.h>
30 #else
31 //#warning "No network support enabled in http_util"
32 #endif
33
34 namespace Botan {
35
36 namespace HTTP {
37
38 namespace {
39
40 /*
41 * Connect to a host, write some bytes, then read until the server
42 * closes the socket.
43 */
44 std::string http_transact(const std::string& hostname,
45 const std::string& message)
46 {
47 #if defined(BOTAN_HAS_BOOST_ASIO)
48 using namespace boost::asio::ip;
49
50 boost::asio::ip::tcp::iostream tcp;
51
52 tcp.connect(hostname, "http");
53
54 if(!tcp)
55 throw HTTP_Error("HTTP connection to " + hostname + " failed");
56
57 tcp << message;
58 tcp.flush();
59
60 std::ostringstream oss;
61 oss << tcp.rdbuf();
62
63 return oss.str();
64 #elif defined(BOTAN_TARGET_OS_HAS_SOCKETS)
65
66 hostent* host_addr = ::gethostbyname(hostname.c_str());
67 uint16_t port = 80;
68
69 if(!host_addr)
70 throw HTTP_Error("Name resolution failed for " + hostname);
71
72 if(host_addr->h_addrtype != AF_INET) // FIXME
73 throw HTTP_Error("Hostname " + hostname + " resolved to non-IPv4 address");
74
75 struct socket_raii {
76 socket_raii(int fd) : m_fd(fd) {}
77 ~socket_raii() { ::close(m_fd); }
78 int m_fd;
79 };
80
81 int fd = ::socket(PF_INET, SOCK_STREAM, 0);
82 if(fd == -1)
83 throw HTTP_Error("Unable to create TCP socket");
84 socket_raii raii(fd);
85
86 sockaddr_in socket_info;
87 ::memset(&socket_info, 0, sizeof(socket_info));
88 socket_info.sin_family = AF_INET;
89 socket_info.sin_port = htons(port);
90
91 ::memcpy(&socket_info.sin_addr,
92 host_addr->h_addr,
93 host_addr->h_length);
94
95 socket_info.sin_addr = *reinterpret_cast<struct in_addr*>(host_addr->h_addr); // FIXME
96
97 if(::connect(fd, reinterpret_cast<sockaddr*>(&socket_info), sizeof(struct sockaddr)) != 0)
98 throw HTTP_Error("HTTP connection to " + hostname + " failed");
99
100 size_t sent_so_far = 0;
101 while(sent_so_far != message.size())
102 {
103 size_t left = message.size() - sent_so_far;
104 ssize_t sent = ::write(fd, &message[sent_so_far], left);
105
106 if(sent < 0)
107 throw HTTP_Error("write to HTTP server failed, error '" + std::string(::strerror(errno)) + "'");
108 else
109 sent_so_far += static_cast<size_t>(sent);
110 }
111
112 std::ostringstream oss;
113 std::vector<char> buf(1024); // arbitrary size
114 while(true)
115 {
116 ssize_t got = ::read(fd, buf.data(), buf.size());
117
118 if(got < 0)
119 throw HTTP_Error("read from HTTP server failed, error '" + std::string(::strerror(errno)) + "'");
120 else if(got > 0)
121 oss.write(buf.data(), static_cast<std::streamsize>(got));
122 else
123 break; // EOF
124 }
125 return oss.str();
126
127 #else
128 throw HTTP_Error("Cannot connect to " + hostname + ": network code disabled in build");
129 #endif
130 }
131
132 }
133
134 std::string url_encode(const std::string& in)
135 {
136 std::ostringstream out;
137
138 for(auto c : in)
139 {
140 if(c >= 'A' && c <= 'Z')
141 out << c;
142 else if(c >= 'a' && c <= 'z')
143 out << c;
144 else if(c >= '0' && c <= '9')
145 out << c;
146 else if(c == '-' || c == '_' || c == '.' || c == '~')
147 out << c;
148 else
149 out << '%' << hex_encode(reinterpret_cast<uint8_t*>(&c), 1);
150 }
151
152 return out.str();
153 }
154
155 std::ostream& operator<<(std::ostream& o, const Response& resp)
156 {
157 o << "HTTP " << resp.status_code() << " " << resp.status_message() << "\n";
158 for(auto h : resp.headers())
159 o << "Header '" << h.first << "' = '" << h.second << "'\n";
160 o << "Body " << std::to_string(resp.body().size()) << " bytes:\n";
161 o.write(reinterpret_cast<const char*>(&resp.body()[0]), resp.body().size());
162 return o;
163 }
164
165 Response http_sync(http_exch_fn http_transact,
166 const std::string& verb,
167 const std::string& url,
168 const std::string& content_type,
169 const std::vector<uint8_t>& body,
170 size_t allowable_redirects)
171 {
172 if(url.empty())
173 throw HTTP_Error("URL empty");
174
175 const auto protocol_host_sep = url.find("://");
176 if(protocol_host_sep == std::string::npos)
177 throw HTTP_Error("Invalid URL '" + url + "'");
178
179 const auto host_loc_sep = url.find('/', protocol_host_sep + 3);
180
181 std::string hostname, loc;
182
183 if(host_loc_sep == std::string::npos)
184 {
185 hostname = url.substr(protocol_host_sep + 3, std::string::npos);
186 loc = "/";
187 }
188 else
189 {
190 hostname = url.substr(protocol_host_sep + 3, host_loc_sep-protocol_host_sep-3);
191 loc = url.substr(host_loc_sep, std::string::npos);
192 }
193
194 std::ostringstream outbuf;
195
196 outbuf << verb << " " << loc << " HTTP/1.0\r\n";
197 outbuf << "Host: " << hostname << "\r\n";
198
199 if(verb == "GET")
200 {
201 outbuf << "Accept: */*\r\n";
202 outbuf << "Cache-Control: no-cache\r\n";
203 }
204 else if(verb == "POST")
205 outbuf << "Content-Length: " << body.size() << "\r\n";
206
207 if(!content_type.empty())
208 outbuf << "Content-Type: " << content_type << "\r\n";
209 outbuf << "Connection: close\r\n\r\n";
210 outbuf.write(reinterpret_cast<const char*>(body.data()), body.size());
211
212 std::istringstream io(http_transact(hostname, outbuf.str()));
213
214 std::string line1;
215 std::getline(io, line1);
216 if(!io || line1.empty())
217 throw HTTP_Error("No response");
218
219 std::stringstream response_stream(line1);
220 std::string http_version;
221 unsigned int status_code;
222 std::string status_message;
223
224 response_stream >> http_version >> status_code;
225
226 std::getline(response_stream, status_message);
227
228 if(!response_stream || http_version.substr(0,5) != "HTTP/")
229 throw HTTP_Error("Not an HTTP response");
230
231 std::map<std::string, std::string> headers;
232 std::string header_line;
233 while (std::getline(io, header_line) && header_line != "\r")
234 {
235 auto sep = header_line.find(": ");
236 if(sep == std::string::npos || sep > header_line.size() - 2)
237 throw HTTP_Error("Invalid HTTP header " + header_line);
238 const std::string key = header_line.substr(0, sep);
239
240 if(sep + 2 < header_line.size() - 1)
241 {
242 const std::string val = header_line.substr(sep + 2, (header_line.size() - 1) - (sep + 2));
243 headers[key] = val;
244 }
245 }
246
247 if(status_code == 301 && headers.count("Location"))
248 {
249 if(allowable_redirects == 0)
250 throw HTTP_Error("HTTP redirection count exceeded");
251 return GET_sync(headers["Location"], allowable_redirects - 1);
252 }
253
254 std::vector<uint8_t> resp_body;
255 std::vector<uint8_t> buf(4096);
256 while(io.good())
257 {
258 io.read(reinterpret_cast<char*>(buf.data()), buf.size());
259 resp_body.insert(resp_body.end(), buf.data(), &buf[io.gcount()]);
260 }
261
262 const std::string header_size = search_map(headers, std::string("Content-Length"));
263
264 if(!header_size.empty())
265 {
266 if(resp_body.size() != to_u32bit(header_size))
267 throw HTTP_Error("Content-Length disagreement, header says " +
268 header_size + " got " + std::to_string(resp_body.size()));
269 }
270
271 return Response(status_code, status_message, resp_body, headers);
272 }
273
274 Response http_sync(const std::string& verb,
275 const std::string& url,
276 const std::string& content_type,
277 const std::vector<uint8_t>& body,
278 size_t allowable_redirects)
279 {
280 return http_sync(
281 http_transact,
282 verb,
283 url,
284 content_type,
285 body,
286 allowable_redirects);
287 }
288
289 Response GET_sync(const std::string& url, size_t allowable_redirects)
290 {
291 return http_sync("GET", url, "", std::vector<uint8_t>(), allowable_redirects);
292 }
293
294 Response POST_sync(const std::string& url,
295 const std::string& content_type,
296 const std::vector<uint8_t>& body,
297 size_t allowable_redirects)
298 {
299 return http_sync("POST", url, content_type, body, allowable_redirects);
300 }
301
302 }
303
304 }
Botan::HTTP::http_sync
Response http_sync(http_exch_fn http_transact, const std::string &verb, const std::string &url, const std::string &content_type, const std::vector< uint8_t > &body, size_t allowable_redirects)
Definition: http_util.cpp:165
Botan::to_u32bit
uint32_t to_u32bit(const std::string &str)
Definition: parsing.cpp:18
Botan::search_map
V search_map(const std::map< K, V > &mapping, const K &key, const V &null_result=V())
Definition: stl_util.h:52
Botan::HTTP::Response::body
const std::vector< uint8_t > & body() const
Definition: http_util.h:37
Botan::HTTP::Response
Definition: http_util.h:22
Botan::ASN1::to_string
std::string to_string(const BER_Object &obj)
Definition: asn1_obj.cpp:47
Botan::HTTP::Response::headers
const std::map< std::string, std::string > & headers() const
Definition: http_util.h:39
Botan::HTTP::Response::status_code
unsigned int status_code() const
Definition: http_util.h:35
Botan::HTTP::url_encode
std::string url_encode(const std::string &in)
Definition: http_util.cpp:134
Botan::HTTP::http_exch_fn
std::function< std::string(const std::string &, const std::string &)> http_exch_fn
Definition: http_util.h:68
Botan::HTTP::Response::status_message
std::string status_message() const
Definition: http_util.h:41
Botan::HTTP::POST_sync
Response POST_sync(const std::string &url, const std::string &content_type, const std::vector< uint8_t > &body, size_t allowable_redirects)
Definition: http_util.cpp:294
Botan
Definition: alg_id.cpp:13
Botan::HTTP::operator<<
std::ostream & operator<<(std::ostream &o, const Response &resp)
Definition: http_util.cpp:155
Botan::HTTP::GET_sync
Response GET_sync(const std::string &url, size_t allowable_redirects)
Definition: http_util.cpp:289
m_fd
int m_fd
Definition: system_rng.cpp:55
Botan::HTTP::HTTP_Error
Definition: http_util.h:59