Line data Source code
1 : #include "http_request.hpp"
2 : #include <unordered_map>
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 956 : method get_method(const std::string &method) {
22 : static std::unordered_map<std::string, http::method> methods = {
23 0 : {GET_STR, method::GET},
24 0 : {HEAD_STR, method::HEAD},
25 0 : {POST_STR, method::POST},
26 0 : {PUT_STR, method::PUT},
27 0 : {DELETE_STR, method::DELETE},
28 0 : {TRACE_STR, method::TRACE},
29 0 : {OPTIONS_STR, method::OPTIONS},
30 0 : {CONNECT_STR, method::CONNECT},
31 0 : {PATCH_STR, method::PATCH}
32 3442 : };
33 956 : auto it = methods.find(method);
34 956 : if (it != methods.end()) {
35 953 : return it->second;
36 : }
37 3 : return method::UNKNOWN;
38 226 : }
39 :
40 3591 : const std::string& get_method(http::method method)
41 : {
42 3591 : switch(method){
43 2902 : case method::GET:
44 2902 : return GET_STR;
45 39 : case method::HEAD:
46 39 : return HEAD_STR;
47 390 : case method::POST:
48 390 : return POST_STR;
49 122 : case method::PUT:
50 122 : return PUT_STR;
51 55 : case method::DELETE:
52 55 : return DELETE_STR;
53 3 : case method::TRACE:
54 3 : return TRACE_STR;
55 31 : case method::OPTIONS:
56 31 : return OPTIONS_STR;
57 3 : case method::CONNECT:
58 3 : return CONNECT_STR;
59 43 : case method::PATCH:
60 43 : return PATCH_STR;
61 3 : default:
62 3 : static std::string empty;
63 3 : 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 1775 : void http_request::log(const char* scope, int level) const{
80 : // Log the request with context
81 1775 : LOG_INFO("[{}] {} {}", scope, http::get_method(method_), get_uri());
82 :
83 : // Log headers at debug level
84 1775 : LOG_DEBUG("Headers:");
85 7578 : for(const auto& t: headers_){
86 5803 : LOG_DEBUG(" {}: {}", t.first, t.second);
87 : }
88 1778 : for(const auto& t: proxy_headers_){
89 3 : LOG_DEBUG(" (PROXY) {}: {}", t.first, t.second);
90 : }
91 :
92 : // Log body if present (at trace level)
93 1775 : if(!content_.empty()){
94 106 : LOG_TRACE("Body: {} bytes", content_.size());
95 106 : if(content_.size() <= 500) {
96 98 : std::istringstream f(content_);
97 98 : std::string line;
98 196 : while (std::getline(f, line)) {
99 98 : LOG_TRACE(" {}", line);
100 : }
101 98 : } else {
102 8 : LOG_TRACE(" {} (truncated)", content_.substr(0, 500));
103 : }
104 : }
105 1775 : }
106 :
107 0 : void http_request::set_chunked_callback(std::function<void(int, const std::string&)> callback){
108 0 : on_chunked_ = std::move(callback);
109 0 : }
110 :
111 864 : std::function<void(int, const std::string&)> http_request::get_chunked_callback(){
112 864 : return on_chunked_;
113 : }
114 :
115 : std::shared_ptr<http_request>
116 6 : http_request::create_http_request(http::method method, const std::string& url, const std::string& unix_socket){
117 6 : auto request = std::make_shared<http_request>();
118 6 : if(request->set_url(url)){
119 6 : request->set_method(method);
120 12 : request->add_header(http::header::accept, "*/*");
121 6 : if(!unix_socket.empty()){
122 0 : request->set_unix_socket(unix_socket);
123 : }
124 6 : return request;
125 : }
126 : // return an invalid request (something failed while parsing the URL)
127 0 : return std::make_shared<http_request>();
128 6 : }
129 :
130 141 : std::string http_request::get_base_path() const{
131 141 : std::string base_path = is_ssl() ? "https://" : "http://";
132 141 : base_path += is_default_port() ? get_host() : get_host() + ":" + get_port();
133 141 : return base_path;
134 0 : }
135 :
136 141 : std::string http_request::get_url() const{
137 141 : return get_base_path() + get_uri();
138 : }
139 :
140 995 : bool http_request::set_url(const std::string& url){
141 995 : static const std::regex uri_regex(R"((wss?|https?):\/\/(?:(.*):(.*)@)?([a-zA-Z\.\-0-9_]+)(?::(\d+))*(.*))");
142 995 : std::match_results<std::string::const_iterator> results;
143 995 : if (std::regex_match(url, results, uri_regex)) {
144 995 : std::string protocol = results[1];
145 995 : std::string username = results[2];
146 995 : std::string password = results[3];
147 995 : std::string host = results[4];
148 995 : std::string port = results[5];
149 995 : std::string uri = results[6];
150 995 : set_host(host);
151 995 : if(!port.empty()){
152 869 : set_port(port);
153 : }
154 995 : set_protocol(protocol);
155 995 : set_ssl(protocol==https || protocol==wss);
156 1049 : set_uri(uri.empty() ? "/" : uri);
157 :
158 :
159 : // set upgrade headers if it is a websocket
160 995 : if(protocol==wss || protocol==ws){
161 84 : set_header(http::header::connection, "upgrade");
162 126 : set_header(http::header::upgrade, "websocket");
163 : }
164 :
165 995 : return true;
166 995 : }
167 0 : return false;
168 995 : }
169 :
170 3 : std::shared_ptr<http_request> http_request::create_http_request(const std::string& method, const std::string& url){
171 6 : return create_http_request(http::get_method(method), url);
172 : }
173 :
174 3 : bool http_request::has_query_parameters() const{
175 3 : return !uri_params_.empty();
176 : }
177 :
178 0 : bool http_request::has_resource() const{
179 0 : return !resource_.empty();
180 : }
181 :
182 0 : const std::string& http_request::get_body() const{
183 0 : return content_;
184 : }
185 :
186 281 : std::string& http_request::get_body(){
187 281 : return content_;
188 : }
189 :
190 0 : std::string& http_request::get_resource(){
191 0 : return resource_;
192 : }
193 :
194 0 : const std::string& http_request::get_resource() const{
195 0 : return resource_;
196 : }
197 :
198 0 : void http_request::set_resource(const std::string& resource){
199 : // update resource
200 0 : resource_ = resource;
201 : // refresh uri
202 0 : refresh_uri();
203 0 : }
204 :
205 3 : bool http_request::has_uri_parameters() const{
206 3 : return !uri_params_.empty();
207 : }
208 :
209 0 : bool http_request::has_uri_value(const std::string& key, const std::string& value) const{
210 0 : auto position = uri_params_.find(key);
211 0 : return position != uri_params_.end() && position->second == value;
212 : }
213 :
214 30 : bool http_request::has_uri_parameter(const std::string& key) const{
215 30 : return uri_params_.find(key) != uri_params_.end();
216 : }
217 :
218 0 : const std::multimap<std::string, std::string>& http_request::get_uri_parameters() const{
219 0 : return uri_params_;
220 : }
221 :
222 22 : const std::string& http_request::get_uri_parameter(const std::string& key) const{
223 22 : auto position = uri_params_.find(key);
224 22 : if(position != uri_params_.end()){
225 22 : return position->second;
226 : }
227 0 : static const std::string empty;
228 0 : return empty;
229 : }
230 :
231 15 : void http_request::add_uri_parameter(const std::string& key, const std::string& value){
232 : // store uri value
233 15 : uri_params_.emplace(key, value);
234 : // update uri
235 15 : refresh_uri();
236 15 : }
237 :
238 9 : bool http_request::has_content() const{
239 9 : return !content_.empty() || has_header(http::header::content_length);
240 : }
241 :
242 139 : void http_request::set_content(std::string content, std::string content_type){
243 139 : set_content(std::move(content));
244 139 : set_header(http::header::content_type, std::move(content_type));
245 139 : }
246 :
247 148 : void http_request::set_content(std::string content){
248 148 : content_ = std::move(content);
249 148 : set_header(http::header::content_length, boost::lexical_cast<std::string>(content_.size()));
250 148 : }
251 :
252 6 : const std::string& http_request::get_method_string() const{
253 6 : return http::get_method(method_);
254 : }
255 :
256 2015 : method http_request::get_method() const{
257 2015 : return method_;
258 : }
259 :
260 928 : void http_request::set_method(http::method method){
261 : // set method
262 928 : method_ = method;
263 : // force methods with a supposed body to force a default content length
264 928 : if((method_==http::method::POST || method_==http::method::PUT || method==http::method::PATCH) && !has_header(http::header::content_length)){
265 459 : set_header(http::header::content_length, "0");
266 : }
267 928 : }
268 :
269 3 : std::string http_request::get_query_string() const{
270 3 : return get_url_encoded_data(uri_params_);
271 : }
272 :
273 942 : std::string http_request::get_path() const {
274 942 : size_t query_pos = uri_.find('?');
275 942 : if (query_pos != std::string::npos) {
276 91 : return uri_.substr(0, query_pos);
277 : }
278 851 : return uri_;
279 : }
280 :
281 923 : void http_request::set_method(const std::string& method){
282 923 : method_ = http::get_method(method);
283 923 : }
284 :
285 52 : std::string& http_request::get_uri(){
286 52 : return uri_;
287 : }
288 :
289 2785 : const std::string& http_request::get_uri() const{
290 2785 : return uri_;
291 : }
292 :
293 18 : void http_request::refresh_uri(){
294 18 : if(uri_params_.empty()){
295 0 : uri_ = util::url::uri_path_encode(resource_);
296 : }else{
297 18 : uri_ = util::url::uri_path_encode(resource_) + "?" + get_url_encoded_data(uri_params_);
298 : }
299 18 : }
300 :
301 1948 : void http_request::set_uri(const std::string& uri){
302 1948 : std::smatch what;
303 1948 : std::string::const_iterator start = uri.begin();
304 1948 : std::string::const_iterator end = uri.end();
305 :
306 1948 : static const std::regex resource_regex("(\\/[^\\?#]*)");
307 1948 : if(std::regex_search(start, end, what, resource_regex)){
308 1948 : resource_ = util::url::url_decode(std::string(what[0].first, what[1].second));
309 1948 : start = what[0].second;
310 : }
311 :
312 : // Regex will stop at ? or # or end of the uri. Check if there is parameters available for its parsing
313 1948 : if(start!=uri.end() && *start=='?'){
314 158 : ++start;
315 158 : parse_url_encoded_data(start, end, uri_params_);
316 : }
317 :
318 : // just save the original uri to avoid generating it again
319 1948 : uri_ = uri;
320 1948 : }
321 :
322 836 : const std::string& http_request::get_unix_socket(){
323 836 : return unix_socket_;
324 : }
325 :
326 51 : void http_request::set_unix_socket(const std::string& unix_socket){
327 51 : unix_socket_ = unix_socket;
328 51 : }
329 :
330 1907 : void http_request::set_ssl(bool ssl){
331 1907 : ssl_ = ssl;
332 1907 : }
333 :
334 2442 : bool http_request::is_ssl() const{
335 2442 : return ssl_;
336 : }
337 :
338 6 : const std::string& http_request::get_protocol() const{
339 6 : return protocol_;
340 : }
341 :
342 1004 : void http_request::set_protocol(std::string protocol){
343 1004 : protocol_ = std::move(protocol);
344 : // Update SSL flag based on protocol
345 1004 : ssl_ = (protocol_ == "https" || protocol_ == "wss");
346 1004 : }
347 :
348 887 : void http_request::set_port(const std::string& port){
349 887 : port_ = port;
350 : // update host header if the port is not the default one (80 or 443)
351 887 : if(port!=https_port && port!=http_port){
352 875 : set_header(http::header::host, host_ + ":" + port_);
353 : }
354 887 : }
355 :
356 150 : bool http_request::is_default_port() const{
357 150 : return port_.empty() ? true : ((ssl_ && https_port == port_) || http_port==port_);
358 : }
359 :
360 7030 : const std::string& http_request::get_port() const{
361 7030 : return port_.empty() ? (ssl_ ? https_port : http_port) : port_;
362 : }
363 :
364 1930 : void http_request::set_host(std::string host){
365 1930 : auto position = host.find(':');
366 1930 : if(position==std::string::npos){
367 1109 : host_ = std::move(host);
368 : }else{
369 821 : host_ = host.substr(0, position);
370 821 : port_ = host.substr(position+1, std::string::npos);
371 : }
372 :
373 : // use host always in lowercase
374 1930 : boost::algorithm::to_lower(host_);
375 :
376 : // update host header
377 1930 : if(get_port()!=https_port && get_port()!=http_port){
378 862 : set_header(http::header::host, host_ + ":" + port_);
379 : }else{
380 1068 : set_header(http::header::host, host_);
381 : }
382 1930 : }
383 :
384 4042 : const std::string& http_request::get_host() const{
385 4042 : return host_;
386 : }
387 :
388 49 : cookie_store& http_request::get_cookie_store(){
389 49 : return cookie_store_;
390 : }
391 :
392 2982 : void http_request::process_header(std::string key, std::string value){
393 : // adjust host
394 2982 : if(boost::iequals(key, http::header::host)){
395 917 : set_host(std::move(value));
396 : }else{
397 : // detect chunked transfer encoding
398 2065 : if(boost::iequals(key, http::header::transfer_encoding) && boost::iequals(value, "chunked")){
399 10 : chunked_transfer_ = true;
400 : }
401 : // handle header by parent
402 2065 : headers::process_header(std::move(key), std::move(value));
403 : }
404 2982 : }
405 :
406 869 : void http_request::to_buffer(std::vector<boost::asio::const_buffer>& buffer) const{
407 869 : buffer.emplace_back(boost::asio::buffer(http::get_method(method_)));
408 869 : buffer.emplace_back(boost::asio::buffer(misc_strings::space));
409 869 : buffer.emplace_back(boost::asio::buffer(get_uri()));
410 869 : buffer.emplace_back(boost::asio::buffer(misc_strings::space));
411 869 : buffer.emplace_back(boost::asio::buffer(misc_strings::http_1_1));
412 869 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
413 :
414 869 : std::set<std::string> proxy_headers;
415 :
416 : // replace headers with proxy headers (if any)
417 869 : for(const auto& [key, value] : proxy_headers_){
418 0 : buffer.emplace_back(boost::asio::buffer(key));
419 0 : buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
420 0 : buffer.emplace_back(boost::asio::buffer(value));
421 0 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
422 0 : proxy_headers.emplace(key);
423 : }
424 :
425 : // push headers
426 3736 : for(const auto& [key, value] : headers_){
427 2867 : if(proxy_headers.find(key)==proxy_headers.end()){
428 2867 : buffer.emplace_back(boost::asio::buffer(key));
429 2867 : buffer.emplace_back(boost::asio::buffer(misc_strings::name_value_separator));
430 2867 : buffer.emplace_back(boost::asio::buffer(value));
431 2867 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
432 : }
433 : }
434 :
435 869 : buffer.emplace_back(boost::asio::buffer(misc_strings::crlf));
436 :
437 869 : if(!content_.empty()){
438 106 : buffer.emplace_back(boost::asio::buffer(content_));
439 : }
440 869 : }
441 :
442 9 : size_t http_request::get_size(){
443 : // Return the size of the content/payload
444 9 : return content_.size();
445 : }
446 :
447 0 : bool http_request::end_stream(){
448 0 : return true;
449 : }
450 :
451 : }
|