Line data Source code
1 : #include "http_request.hpp"
2 : #include <string_view>
3 : #include <set>
4 : #include "../../util/logger.hpp"
5 :
6 : namespace thinger::http {
7 :
8 : using namespace util::url;
9 :
10 :
11 : static const std::string GET_STR = "GET";
12 : static const std::string HEAD_STR = "HEAD";
13 : static const std::string POST_STR = "POST";
14 : static const std::string PUT_STR = "PUT";
15 : static const std::string DELETE_STR = "DELETE";
16 : static const std::string TRACE_STR = "TRACE";
17 : static const std::string OPTIONS_STR = "OPTIONS";
18 : static const std::string CONNECT_STR = "CONNECT";
19 : static const std::string PATCH_STR = "PATCH";
20 :
21 35434 : method get_method(const std::string &method) {
22 : // a few short names: comparing them is cheaper than hashing
23 : static constexpr std::pair<std::string_view, http::method> methods[] = {
24 : {"GET", method::GET},
25 : {"POST", method::POST},
26 : {"PUT", method::PUT},
27 : {"DELETE", method::DELETE},
28 : {"PATCH", method::PATCH},
29 : {"HEAD", method::HEAD},
30 : {"OPTIONS", method::OPTIONS},
31 : {"TRACE", method::TRACE},
32 : {"CONNECT", method::CONNECT}
33 : };
34 42637 : for (const auto& [name, value] : methods) {
35 42631 : if (name == method) return value;
36 : }
37 6 : return method::UNKNOWN;
38 : }
39 :
40 196403 : const std::string& get_method(http::method method)
41 : {
42 196403 : switch(method){
43 182081 : case method::GET:
44 182081 : return GET_STR;
45 660 : case method::HEAD:
46 660 : return HEAD_STR;
47 10770 : case method::POST:
48 10770 : return POST_STR;
49 972 : case method::PUT:
50 972 : return PUT_STR;
51 676 : case method::DELETE:
52 676 : return DELETE_STR;
53 3 : case method::TRACE:
54 3 : return TRACE_STR;
55 516 : case method::OPTIONS:
56 516 : return OPTIONS_STR;
57 51 : case method::CONNECT:
58 51 : return CONNECT_STR;
59 674 : case method::PATCH:
60 674 : return PATCH_STR;
61 0 : default:
62 0 : static std::string empty;
63 0 : return empty;
64 : }
65 : }
66 :
67 : /*
68 : void http_request::debug(std::ostream& os) const{
69 : os << "[HTTP REQUEST] " + get_url() << std::endl;
70 : os << " > HOST " << get_host() << ":" << get_port() << (is_ssl() ? " (SSL)" : " (NO SSL)") << std::endl;
71 : os << " > " << http::get_method(method_) << " " << get_uri() << std::endl;
72 : debug_headers(os);
73 : if(!content_.empty()){
74 : os << std::endl << content_;
75 : }
76 : }
77 : */
78 :
79 31919 : void http_request::log(const char* scope, const body_logging& options) const{
80 31919 : if(!LOG_INFO_ENABLED()) return;
81 348 : LOG_INFO("[{}] {} {}", scope, http::get_method(method_), loggable_uri(get_uri(), options.redact));
82 348 : if(LOG_DEBUG_ENABLED()){
83 336 : LOG_DEBUG("Headers:");
84 336 : log_headers(options);
85 : }
86 : // The body, unless it is not read yet: the size it announces
87 348 : log_body(content_, content_.empty() ? get_content_length() : content_.size(), options);
88 : }
89 :
90 6 : void http_request::set_chunked_callback(std::function<void(int, const std::string&)> callback){
91 6 : on_chunked_ = std::move(callback);
92 6 : }
93 :
94 8406 : std::function<void(int, const std::string&)> http_request::get_chunked_callback(){
95 8406 : return on_chunked_;
96 : }
97 :
98 : std::shared_ptr<http_request>
99 4205 : http_request::create_http_request(http::method method, const std::string& url, const std::string& unix_socket){
100 4205 : auto request = std::make_shared<http_request>();
101 4205 : if(request->set_url(url)){
102 4205 : request->set_method(method);
103 8410 : request->add_header(http::header::accept, "*/*");
104 4205 : if(!unix_socket.empty()){
105 0 : request->set_unix_socket(unix_socket);
106 : }
107 4205 : return request;
108 : }
109 : // return an invalid request (something failed while parsing the URL)
110 0 : return std::make_shared<http_request>();
111 4205 : }
112 :
113 582 : std::string http_request::get_base_path() const{
114 582 : std::string base_path = is_ssl() ? "https://" : "http://";
115 582 : base_path += is_default_port() ? get_host() : get_host() + ":" + get_port();
116 582 : return base_path;
117 0 : }
118 :
119 465 : std::string http_request::get_url() const{
120 465 : return get_base_path() + get_uri();
121 : }
122 :
123 13257 : bool http_request::set_url(const std::string& url){
124 : // Longer URLs are refused before the regex, whose matching recurses per character in
125 : // libstdc++ (a Location comes from the peer)
126 13257 : if(url.size() > 8192) return false;
127 13245 : static const std::regex uri_regex(R"((wss?|https?):\/\/(?:(.*):(.*)@)?([a-zA-Z\.\-0-9_]+)(?::(\d+))*(.*))");
128 13245 : std::match_results<std::string::const_iterator> results;
129 13245 : if (std::regex_match(url, results, uri_regex)) {
130 13230 : std::string protocol = results[1];
131 13230 : std::string username = results[2];
132 13230 : std::string password = results[3];
133 13230 : std::string host = results[4];
134 13230 : std::string port = results[5];
135 13230 : std::string uri = results[6];
136 13230 : set_host(host);
137 13230 : if(!port.empty()){
138 8281 : set_port(port);
139 : }
140 13230 : set_protocol(protocol);
141 13230 : set_ssl(protocol==https || protocol==wss);
142 13284 : set_uri(uri.empty() ? "/" : uri);
143 :
144 :
145 : // set upgrade headers if it is a websocket
146 13230 : if(protocol==wss || protocol==ws){
147 530 : set_header(http::header::connection, "upgrade");
148 795 : set_header(http::header::upgrade, "websocket");
149 : }
150 :
151 13230 : return true;
152 13230 : }
153 15 : return false;
154 13245 : }
155 :
156 3 : std::shared_ptr<http_request> http_request::create_http_request(const std::string& method, const std::string& url){
157 6 : return create_http_request(http::get_method(method), url);
158 : }
159 :
160 3 : bool http_request::has_query_parameters() const{
161 3 : return !uri_params_.empty();
162 : }
163 :
164 0 : bool http_request::has_resource() const{
165 0 : return !resource_.empty();
166 : }
167 :
168 0 : const std::string& http_request::get_body() const{
169 0 : return content_;
170 : }
171 :
172 29925 : std::string& http_request::get_body(){
173 29925 : return content_;
174 : }
175 :
176 120338 : std::string& http_request::get_resource(){
177 120338 : return resource_;
178 : }
179 :
180 0 : const std::string& http_request::get_resource() const{
181 0 : return resource_;
182 : }
183 :
184 60153 : void http_request::set_resource(const std::string& resource){
185 : // update resource
186 60153 : resource_ = resource;
187 : // refresh uri
188 60153 : refresh_uri();
189 60153 : }
190 :
191 3 : bool http_request::has_uri_parameters() const{
192 3 : return !uri_params_.empty();
193 : }
194 :
195 0 : bool http_request::has_uri_value(const std::string& key, const std::string& value) const{
196 0 : auto position = uri_params_.find(key);
197 0 : return position != uri_params_.end() && position->second == value;
198 : }
199 :
200 1068 : bool http_request::has_uri_parameter(const std::string& key) const{
201 1068 : return uri_params_.find(key) != uri_params_.end();
202 : }
203 :
204 120313 : const std::multimap<std::string, std::string>& http_request::get_uri_parameters() const{
205 120313 : return uri_params_;
206 : }
207 :
208 298 : const std::string& http_request::get_uri_parameter(const std::string& key) const{
209 298 : auto position = uri_params_.find(key);
210 298 : if(position != uri_params_.end()){
211 298 : return position->second;
212 : }
213 0 : static const std::string empty;
214 0 : return empty;
215 : }
216 :
217 60171 : void http_request::add_uri_parameter(const std::string& key, const std::string& value){
218 : // store uri value
219 60171 : uri_params_.emplace(key, value);
220 : // update uri
221 60171 : refresh_uri();
222 60171 : }
223 :
224 9 : bool http_request::has_content() const{
225 9 : return !content_.empty() || has_header(http::header::content_length);
226 : }
227 :
228 979 : void http_request::set_content(std::string content, std::string content_type){
229 979 : set_content(std::move(content));
230 979 : set_header(http::header::content_type, std::move(content_type));
231 979 : }
232 :
233 1009 : void http_request::set_content(std::string content){
234 1009 : content_ = std::move(content);
235 1009 : replace_header(std::string(http::header::content_length), boost::lexical_cast<std::string>(content_.size()));
236 1009 : }
237 :
238 6 : const std::string& http_request::get_method_string() const{
239 6 : return http::get_method(method_);
240 : }
241 :
242 76834 : void http_request::set_method(http::method method){
243 : // set method
244 76834 : method_ = method;
245 : // force methods with a supposed body to force a default content length
246 76834 : if((method_==http::method::POST || method_==http::method::PUT || method==http::method::PATCH) && !has_header(http::header::content_length)){
247 3735 : replace_header(std::string(http::header::content_length), "0");
248 : }
249 76834 : }
250 :
251 3 : std::string http_request::get_query_string() const{
252 3 : return get_url_encoded_data(uri_params_);
253 : }
254 :
255 4019 : std::string http_request::get_path() const {
256 4019 : size_t query_pos = uri_.find('?');
257 4019 : if (query_pos != std::string::npos) {
258 47 : return uri_.substr(0, query_pos);
259 : }
260 3972 : return uri_;
261 : }
262 :
263 35401 : void http_request::set_method(const std::string& method){
264 35401 : method_ = http::get_method(method);
265 35401 : }
266 :
267 211139 : std::string& http_request::get_uri(){
268 211139 : return uri_;
269 : }
270 :
271 10627 : const std::string& http_request::get_uri() const{
272 10627 : return uri_;
273 : }
274 :
275 120330 : void http_request::refresh_uri(){
276 120330 : if(uri_params_.empty()){
277 60153 : uri_ = util::url::uri_path_encode(resource_);
278 : }else{
279 60177 : uri_ = util::url::uri_path_encode(resource_) + "?" + get_url_encoded_data(uri_params_);
280 : }
281 120330 : }
282 :
283 231160 : void http_request::set_uri(const std::string& uri){
284 231160 : std::string::const_iterator start = uri.begin();
285 231160 : std::string::const_iterator end = uri.end();
286 :
287 : // The resource goes from the first '/' (wherever it is) up to the first '?' or '#'
288 : // after it, or the end. Without any '/', it is left as is
289 231160 : if(auto slash = uri.find('/'); slash != std::string::npos){
290 176713 : auto resource_end = std::min(uri.find_first_of("?#", slash), uri.size());
291 : // "+" is literal in a path (a space only in the query); empty on an invalid escape.
292 : // Decoded apart, as `uri` may be the resource itself
293 176713 : std::string resource;
294 176713 : if (!util::url::detail::path_decode(std::string_view(uri).substr(slash, resource_end - slash), resource)) {
295 14745 : resource.clear();
296 : }
297 176713 : resource_ = std::move(resource);
298 176713 : start = uri.begin() + static_cast<std::ptrdiff_t>(resource_end);
299 176713 : }
300 :
301 : // Query parameters follow the resource (or start the uri, if it has no resource)
302 231160 : if(start!=uri.end() && *start=='?'){
303 26064 : ++start;
304 26064 : parse_url_encoded_data(start, end, uri_params_);
305 : }
306 :
307 : // just save the original uri to avoid generating it again
308 231160 : uri_ = uri;
309 231160 : }
310 :
311 24851 : const std::string& http_request::get_unix_socket() const{
312 24851 : return unix_socket_;
313 : }
314 :
315 859 : void http_request::set_unix_socket(const std::string& unix_socket){
316 859 : unix_socket_ = unix_socket;
317 859 : }
318 :
319 36748 : void http_request::set_ssl(bool ssl){
320 36748 : ssl_ = ssl;
321 36748 : }
322 :
323 52121 : bool http_request::is_ssl() const{
324 52121 : return ssl_;
325 : }
326 :
327 6 : const std::string& http_request::get_protocol() const{
328 6 : return protocol_;
329 : }
330 :
331 13239 : void http_request::set_protocol(std::string protocol){
332 13239 : protocol_ = std::move(protocol);
333 : // Update SSL flag based on protocol
334 13239 : ssl_ = (protocol_ == "https" || protocol_ == "wss");
335 13239 : }
336 :
337 8302 : void http_request::set_port(const std::string& port){
338 8302 : port_ = port;
339 : // update host header if the port is not the default one (80 or 443)
340 8302 : if(port!=https_port && port!=http_port){
341 8248 : set_header(http::header::host, host_ + ":" + port_);
342 : }
343 8302 : }
344 :
345 591 : bool http_request::is_default_port() const{
346 591 : return port_.empty() ? true : ((ssl_ && https_port == port_) || http_port==port_);
347 : }
348 :
349 237970 : const std::string& http_request::get_port() const{
350 237970 : return port_.empty() ? (ssl_ ? https_port : http_port) : port_;
351 : }
352 :
353 75696 : void http_request::set_host(std::string host){
354 75696 : set_header(http::header::host, parse_host(std::move(host)));
355 75696 : }
356 :
357 108477 : std::string http_request::parse_host(std::string host){
358 : // A bracketed IPv6 literal ("[::1]:8080") keeps its brackets, as WHATWG URL.host does:
359 : // its port follows the "]". Without a "]" it is the host whole
360 108477 : auto position = host.find(':');
361 108477 : if(!host.empty() && host[0] == '['){
362 24 : auto bracket = host.find(']');
363 24 : position = bracket != std::string::npos && bracket + 1 < host.size() && host[bracket + 1] == ':' ?
364 : bracket + 1 : std::string::npos;
365 : }
366 108477 : if(position==std::string::npos){
367 105519 : host_ = std::move(host);
368 : }else{
369 2958 : host_ = host.substr(0, position);
370 2958 : port_ = host.substr(position+1, std::string::npos);
371 : }
372 :
373 : // use host always in lowercase
374 108477 : ::thinger::util::ascii::to_lower(host_);
375 :
376 : // the value of the Host header
377 108477 : if(get_port()!=https_port && get_port()!=http_port){
378 6458 : return host_ + ":" + port_;
379 : }
380 105248 : return host_;
381 : }
382 :
383 25847 : const std::string& http_request::get_host() const{
384 25847 : return host_;
385 : }
386 :
387 226 : cookie_store& http_request::get_cookie_store(){
388 226 : return cookie_store_;
389 : }
390 :
391 316582 : void http_request::process_header(std::string key, std::string value){
392 : // adjust host
393 316582 : if(::thinger::util::ascii::iequals(key, http::header::host)){
394 : // parsed: already checked
395 32781 : replace_header(std::string(http::header::host), parse_host(std::move(value)));
396 : }else{
397 283801 : if(::thinger::util::ascii::iequals(key, "Expect")){
398 360 : process_expect_header(value);
399 : }
400 : // handle header by parent
401 283801 : headers::process_header(std::move(key), std::move(value));
402 : }
403 316582 : }
404 :
405 360 : void http_request::process_expect_header(std::string_view value){
406 : // A list of expectations, case-insensitive: 100-continue is the only one defined.
407 : // Ignored in HTTP/1.0 requests (they predate it), and empty values, as Go does
408 360 : if(http_version_major_ == 1 && http_version_minor_ == 0) return;
409 : static constexpr std::string_view whitespace = " \t";
410 351 : while(!value.empty()){
411 339 : auto comma = value.find(',');
412 339 : auto item = value.substr(0, comma);
413 339 : auto first = item.find_first_not_of(whitespace);
414 339 : if(first != std::string_view::npos){
415 333 : item = item.substr(first, item.find_last_not_of(whitespace) - first + 1);
416 333 : if(::thinger::util::ascii::iequals(item, "100-continue")){
417 273 : expectation_ = expectation::continue_100;
418 60 : }else if(expectation_ == expectation::none){
419 48 : expectation_ = expectation::unsupported;
420 : }
421 : }
422 339 : if(comma == std::string_view::npos) break;
423 15 : value.remove_prefix(comma + 1);
424 : }
425 : }
426 :
427 8389 : void http_request::to_buffer(std::vector<boost::asio::const_buffer>& buffer) const{
428 8389 : to_buffer(buffer, get_uri(), {});
429 8389 : }
430 :
431 8455 : void http_request::to_buffer(std::vector<boost::asio::const_buffer>& buffer, std::string_view request_target,
432 : std::string_view extra_headers) const{
433 8455 : buffer.emplace_back(boost::asio::buffer(http::get_method(method_)));
434 8455 : buffer.emplace_back(boost::asio::buffer(misc_strings::space));
435 8455 : buffer.emplace_back(boost::asio::buffer(request_target.data(), request_target.size()));
436 8455 : buffer.emplace_back(boost::asio::buffer(misc_strings::space));
437 8455 : buffer.emplace_back(boost::asio::buffer(misc_strings::http_1_1));
438 8455 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
439 :
440 8455 : std::set<std::string> proxy_headers;
441 :
442 : // replace headers with proxy headers (if any)
443 8458 : for(const auto& [key, value] : proxy_headers_){
444 3 : buffer.emplace_back(boost::asio::buffer(key));
445 3 : buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
446 3 : buffer.emplace_back(boost::asio::buffer(value));
447 3 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
448 3 : proxy_headers.emplace(key);
449 : }
450 :
451 : // push headers
452 36027 : for(const auto& [key, value] : headers_){
453 27572 : if(proxy_headers.find(key)==proxy_headers.end()){
454 27572 : buffer.emplace_back(boost::asio::buffer(key));
455 27572 : buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
456 27572 : buffer.emplace_back(boost::asio::buffer(value));
457 27572 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
458 : }
459 : }
460 :
461 8455 : if(!extra_headers.empty()){
462 9 : buffer.emplace_back(boost::asio::buffer(extra_headers.data(), extra_headers.size()));
463 : }
464 :
465 8455 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
466 :
467 8455 : if(!content_.empty()){
468 553 : buffer.emplace_back(boost::asio::buffer(content_));
469 : }
470 8455 : }
471 :
472 9 : size_t http_request::get_size(){
473 : // Return the size of the content/payload
474 9 : return content_.size();
475 : }
476 :
477 0 : bool http_request::end_stream(){
478 0 : return true;
479 : }
480 :
481 : }
|