Line data Source code
1 : // Copyright (c) 2011-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/edhttp
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 :
20 : // self
21 : //
22 : #include "edhttp/http_client_server.h"
23 :
24 : #include "edhttp/names.h"
25 : #include "edhttp/uri.h"
26 : #include "edhttp/version.h"
27 :
28 :
29 : // snaplogger
30 : //
31 : #include <snaplogger/message.h>
32 :
33 :
34 : // eventdispatcher
35 : //
36 : #include <eventdispatcher/exception.h>
37 :
38 :
39 : // libaddr
40 : //
41 : #include <libaddr/addr_parser.h>
42 :
43 :
44 : // snapdev
45 : //
46 : #include <snapdev/not_reached.h>
47 :
48 :
49 : // C++
50 : //
51 : #include <algorithm>
52 : #include <iostream>
53 : #include <sstream>
54 :
55 :
56 : // last include
57 : //
58 : #include <snapdev/poison.h>
59 :
60 :
61 :
62 : namespace edhttp
63 : {
64 :
65 :
66 : namespace
67 : {
68 :
69 : char const g_base64[] =
70 : {
71 : // 8x8 characters
72 : 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
73 : 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
74 : 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
75 : 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
76 : 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
77 : 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
78 : 'w', 'x', 'y', 'z', '0', '1', '2', '3',
79 : '4', '5', '6', '7', '8', '9', '+', '/'
80 : };
81 :
82 : }
83 : // no name namespace
84 :
85 :
86 :
87 0 : addr::addr_range::vector_t http_request::get_address_ranges() const
88 : {
89 0 : return f_address_ranges;
90 : }
91 :
92 :
93 0 : bool http_request::unique_host() const
94 : {
95 0 : bool first(true);
96 0 : std::string hostname;
97 0 : for(auto const & r : f_address_ranges)
98 : {
99 0 : if(first)
100 : {
101 0 : first = false;
102 0 : hostname = r.get_from().get_hostname();
103 : }
104 0 : else if(hostname != r.get_from().get_hostname())
105 : {
106 0 : return false;
107 : }
108 : }
109 :
110 0 : return true;
111 : }
112 :
113 :
114 : /** \brief Retrieve the hostname.
115 : *
116 : * This function returns the host name attached to this request. The
117 : * hostname is expected to be a domain name although it may also be
118 : * an IP address.
119 : *
120 : * If no addresses were specified, this function returns an empty string.
121 : *
122 : * The IP address may be an IPv4 or an IPv6 address.
123 : *
124 : * \note
125 : * The http_request class supports any number of IP address which the
126 : * HTTP client can make use of to try to connect to the destination.
127 : * (i.e. if the first IP fails, then try the second one, etc.)
128 : *
129 : * \warning
130 : * The host and port are expected to be the same in all the addresses
131 : * which is why this function only returns the hostname found in the
132 : * first address.
133 : *
134 : * \return The host name or IP of the specified address.
135 : */
136 0 : std::string http_request::get_host() const
137 : {
138 0 : if(f_address_ranges.empty())
139 : {
140 0 : return std::string();
141 : }
142 :
143 0 : std::string hostname(f_address_ranges[0].get_from().get_hostname());
144 0 : if(hostname.empty())
145 : {
146 0 : return f_address_ranges[0].get_from().to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_ONLY);
147 : }
148 :
149 0 : return hostname;
150 : }
151 :
152 :
153 0 : bool http_request::unique_port() const
154 : {
155 0 : int port(-1);
156 0 : for(auto const & r : f_address_ranges)
157 : {
158 0 : if(port == -1)
159 : {
160 0 : port = r.get_from().get_port();
161 : }
162 0 : else if(port != r.get_from().get_port())
163 : {
164 0 : return false;
165 : }
166 : }
167 :
168 0 : return true;
169 : }
170 :
171 :
172 0 : int http_request::get_port() const
173 : {
174 0 : if(f_address_ranges.empty())
175 : {
176 0 : return -1;
177 : }
178 :
179 0 : return f_address_ranges[0].get_from().get_port();
180 : }
181 :
182 :
183 0 : std::string http_request::get_agent_name() const
184 : {
185 0 : return f_agent_name;
186 : }
187 :
188 :
189 0 : void http_request::set_address_ranges(addr::addr_range::vector_t const & address_ranges)
190 : {
191 0 : f_address_ranges = address_ranges;
192 0 : }
193 :
194 :
195 0 : std::string http_request::get_command() const
196 : {
197 0 : return f_command;
198 : }
199 :
200 :
201 0 : std::string http_request::get_path() const
202 : {
203 0 : return f_path;
204 : }
205 :
206 :
207 0 : std::string http_request::get_header(std::string const & name) const
208 : {
209 0 : if(f_headers.find(name) == f_headers.end())
210 : {
211 0 : return "";
212 : }
213 0 : return f_headers.at(name);
214 : }
215 :
216 :
217 0 : std::string http_request::get_post(std::string const & name) const
218 : {
219 0 : if(f_post.find(name) == f_post.end())
220 : {
221 0 : return "";
222 : }
223 0 : return f_post.at(name);
224 : }
225 :
226 :
227 0 : std::string http_request::get_body() const
228 : {
229 0 : return f_body;
230 : }
231 :
232 :
233 0 : std::string http_request::get_request(bool keep_alive) const
234 : {
235 0 : std::stringstream request;
236 :
237 : // first we generate the body, that way we define its size
238 : // and also the content type in case of a POST
239 :
240 : // we will get a copy of the body as required because this
241 : // function is constant and we do not want to modify f_body
242 0 : std::string body;
243 0 : std::string content_type;
244 :
245 0 : if(f_has_attachment)
246 : {
247 0 : request << (f_command.empty() ? g_name_edhttp_method_post : f_command.c_str())
248 0 : << ' ' << f_path << ' ' << g_name_edhttp_http_1_1 << "\r\n";
249 :
250 0 : throw edhttp_client_server_logic_error("http_client_server.cpp:get_request(): attachments not supported yet");
251 : }
252 0 : else if(f_has_post)
253 : {
254 : // TODO: support the case where the post variables are passed using
255 : // a GET and a query string
256 0 : request << (f_command.empty() ? g_name_edhttp_method_post : f_command.c_str())
257 0 : << ' ' << f_path << ' ' << g_name_edhttp_http_1_1 << "\r\n";
258 0 : content_type = "application/x-www-form-urlencoded";
259 :
260 0 : body = "";
261 0 : for(auto it(f_post.begin()); it != f_post.end(); ++it)
262 : {
263 0 : if(!body.empty())
264 : {
265 : // separate parameters by ampersands
266 0 : body += "&";
267 : }
268 : // TODO: escape & and such
269 0 : body += it->first + "=" + it->second;
270 : }
271 : }
272 0 : else if(f_has_data)
273 : {
274 0 : request << (f_command.empty() ? g_name_edhttp_method_post : f_command.c_str())
275 0 : << ' ' << f_path << ' ' << g_name_edhttp_http_1_1 << "\r\n";
276 0 : body = f_body;
277 : }
278 0 : else if(f_has_body)
279 : {
280 0 : request << (f_command.empty() ? g_name_edhttp_method_get : f_command.c_str())
281 0 : << ' ' << f_path << ' ' << g_name_edhttp_http_1_1 << "\r\n";
282 0 : body = f_body;
283 : }
284 : else
285 : {
286 0 : request << (f_command.empty() ? g_name_edhttp_method_get : f_command.c_str())
287 0 : << ' ' << f_path << ' ' << g_name_edhttp_http_1_1 << "\r\n";
288 : // body is empty by default
289 : //body = "";
290 : }
291 :
292 : // place Host first because some servers are that stupid
293 0 : request << g_name_edhttp_field_host << ": " << get_host() << "\r\n";
294 :
295 0 : bool found_user_agent(false);
296 0 : for(auto it(f_headers.begin()); it != f_headers.end(); ++it)
297 : {
298 : // make sure we do not output the following fields which are
299 : // managed by our code instead:
300 : //
301 : // Content-Length
302 : //
303 0 : std::string name(it->first);
304 0 : std::transform(name.begin(), name.end(), name.begin(), ::tolower);
305 0 : if((content_type.empty() || name != g_name_edhttp_field_content_type_lowercase)
306 0 : && name != g_name_edhttp_field_content_length_lowercase
307 0 : && name != g_name_edhttp_field_host_lowercase
308 0 : && name != g_name_edhttp_field_connection_lowercase)
309 : {
310 0 : if(name == g_name_edhttp_field_user_agent_lowercase)
311 : {
312 0 : found_user_agent = true;
313 : }
314 0 : request << it->first
315 : << ": "
316 0 : << it->second
317 0 : << "\r\n";
318 : }
319 : }
320 :
321 : // forcing the type? (generally doing so with POSTs)
322 0 : if(!content_type.empty())
323 : {
324 : request
325 : << g_name_edhttp_field_content_type
326 : << ": "
327 : << content_type
328 0 : << "\r\n";
329 : }
330 0 : if(!found_user_agent)
331 : {
332 : request
333 : << g_name_edhttp_field_user_agent
334 : << ": "
335 : << f_agent_name
336 0 : << "/" EDHTTP_VERSION_STRING "\r\n";
337 : }
338 :
339 : // force the connection valid to what the programmer asked (keep-alive by
340 : // default though)
341 : //
342 : // WARNING: according to HTTP/1.1, servers only expect "close" and not
343 : // "keep-alive"; however, it looks like many implementations
344 : // understand both (there is also an "upgrade" which we do not
345 : // support)
346 : request
347 : << g_name_edhttp_field_connection
348 : << ": "
349 : << (keep_alive ? g_name_edhttp_param_keep_alive
350 : : g_name_edhttp_param_close)
351 0 : << "\r\n";
352 :
353 : // end the list with the fields we control:
354 : //
355 : // Content-Length is the size of the body
356 : request
357 : << g_name_edhttp_field_content_length
358 0 : << ": "
359 0 : << body.length()
360 0 : << "\r\n\r\n";
361 :
362 : // TBD: will this work if 'body' includes a '\0'?
363 0 : request << body;
364 :
365 0 : return request.str();
366 : }
367 :
368 :
369 : /** \brief Set the host, port, and path at once.
370 : *
371 : * HTTP accepts full URIs in the GET, POST, etc. line
372 : * so the following would be valid:
373 : *
374 : * GET https://snapwebsites.org/some/path?a=view HTTP/1.1
375 : *
376 : * However, we break it down in a few separate parts instead, because
377 : * (a) we need the host to connect to the server, (b) we need the port
378 : * to connect to the server:
379 : *
380 : * 1. Remove protocol, this defines whether we use plain text (http)
381 : * or encryption (https/ssl)
382 : * 2. Get the port, if not specified after the domain, use the default
383 : * of the specified URI protocol
384 : * 3. Domain name is moved to the 'Host: ...' header
385 : * 4. Path and query string are kept as is
386 : *
387 : * So the example above changes to:
388 : *
389 : * GET /some/path?a=view HTTP/1.1
390 : * Host: snapwebsites.org
391 : *
392 : * We use a plain text connection (http:) and the port is the default
393 : * port for the HTTP protocol (80). That information does not appear
394 : * in the HTTP header.
395 : *
396 : * \param[in] request_uri The URI to save in this HTTP request.
397 : */
398 0 : void http_request::set_uri(std::string const & request_uri)
399 : {
400 0 : uri u(request_uri);
401 :
402 0 : f_address_ranges = u.address_ranges();
403 :
404 : // use set_path() to make sure we get an absolute path
405 : // (which is not the case by default)
406 0 : set_path(u.path());
407 :
408 : // keep the query string parameters if any are defined
409 0 : std::string const q(u.query_string());
410 0 : if(!q.empty())
411 : {
412 0 : f_path += "?";
413 0 : f_path += q;
414 : }
415 0 : }
416 :
417 :
418 0 : void http_request::set_host(std::string const & host)
419 : {
420 0 : int port(get_port());
421 0 : if(port == -1)
422 : {
423 0 : port = 80;
424 : }
425 :
426 : //f_host = host;
427 0 : addr::addr_parser p;
428 0 : p.set_default_port(port);
429 0 : p.set_protocol(IPPROTO_TCP);
430 0 : p.set_sort_order(addr::SORT_IPV6_FIRST | addr::SORT_NO_EMPTY);
431 0 : p.set_allow(addr::allow_t::ALLOW_REQUIRED_ADDRESS, true);
432 0 : p.set_allow(addr::allow_t::ALLOW_MULTI_ADDRESSES_COMMAS, true);
433 0 : p.set_allow(addr::allow_t::ALLOW_MULTI_ADDRESSES_SPACES, true);
434 0 : f_address_ranges = p.parse(host);
435 0 : }
436 :
437 :
438 : /** \brief Define the port.
439 : *
440 : * This function sets the port of all the addresses currently defined in
441 : * the HTTP request object.
442 : *
443 : * \warning
444 : * If no addresses are defined, then this function does nothing. Please
445 : * make sure to first add an address and then call this function to force
446 : * the port as required.
447 : *
448 : * \param[in] port The new port to use to connect.
449 : */
450 0 : void http_request::set_port(int port)
451 : {
452 0 : for(auto & r : f_address_ranges)
453 : {
454 0 : if(r.has_from())
455 : {
456 0 : r.get_from().set_port(port);
457 : }
458 0 : if(r.has_to())
459 : {
460 0 : r.get_to().set_port(port);
461 : }
462 : }
463 0 : }
464 :
465 :
466 0 : void http_request::set_agent_name(std::string const & name)
467 : {
468 0 : f_agent_name = name;
469 0 : }
470 :
471 :
472 0 : void http_request::set_command(std::string const & command)
473 : {
474 0 : f_command = command;
475 0 : }
476 :
477 :
478 0 : void http_request::set_path(std::string const & path)
479 : {
480 : // TODO: better verify path validity
481 0 : if(path.empty())
482 : {
483 0 : f_path = "/";
484 : }
485 0 : else if(path[0] == '/')
486 : {
487 0 : f_path = path;
488 : }
489 : else
490 : {
491 0 : f_path = "/" + path;
492 : }
493 0 : }
494 :
495 :
496 0 : void http_request::set_header(std::string const & name, std::string const & value)
497 : {
498 : // TODO: verify that the header name is compatible/valid
499 : // TODO: for known names, verify that the value is compatible/valid
500 : // TODO: verify the value in various other ways
501 0 : if(value.empty())
502 : {
503 : // remove headers if defined
504 0 : auto h(f_headers.find(value));
505 0 : if(h != f_headers.end())
506 : {
507 0 : f_headers.erase(h);
508 : }
509 : }
510 : else
511 : {
512 : // add header, overwrite if already defined
513 0 : f_headers[name] = value;
514 : }
515 0 : }
516 :
517 :
518 0 : void http_request::set_post(std::string const & name, std::string const & value)
519 : {
520 0 : if(f_has_body || f_has_data)
521 : {
522 0 : throw edhttp_client_server_logic_error("you cannot use set_body(), set_data(), and set_post() on the same http_request object");
523 : }
524 :
525 : // TODO: verify that the name is a valid name for a post variable
526 0 : f_post[name] = value;
527 :
528 0 : f_has_post = true;
529 0 : }
530 :
531 :
532 0 : void http_request::set_basic_auth(std::string const & username, std::string const & secret)
533 : {
534 0 : auto encode = [](std::string const & in, std::string & out)
535 : {
536 : // reset output (just in case)
537 0 : out.clear();
538 :
539 : // WARNING: following algorithm does NOT take any line length
540 : // in account; and it is deadly well optimized
541 0 : unsigned char const *s(reinterpret_cast<unsigned char const *>(in.c_str()));
542 0 : while(*s != '\0')
543 : {
544 : // get 1 to 3 characters of input
545 0 : out += g_base64[s[0] >> 2]; // & 0x3F not required
546 0 : ++s;
547 0 : if(s[0] != '\0')
548 : {
549 0 : out += g_base64[((s[-1] << 4) & 0x30) | ((s[0] >> 4) & 0x0F)];
550 0 : ++s;
551 0 : if(s[0] != '\0')
552 : {
553 : // 24 bits of input uses 4 base64 characters
554 0 : out += g_base64[((s[-1] << 2) & 0x3C) | ((s[0] >> 6) & 0x03)];
555 0 : out += g_base64[s[0] & 0x3F];
556 0 : s++;
557 : }
558 : else
559 : {
560 : // 16 bits of input uses 3 base64 characters + 1 pad
561 0 : out += g_base64[(s[-1] << 2) & 0x3C];
562 0 : out += '=';
563 0 : break;
564 : }
565 : }
566 : else
567 : {
568 : // 8 bits of input uses 2 base64 characters + 2 pads
569 0 : out += g_base64[(s[-1] << 4) & 0x30];
570 0 : out += "==";
571 0 : break;
572 : }
573 : }
574 0 : };
575 :
576 0 : std::string const authorization_token(username + ":" + secret);
577 0 : std::string base64;
578 0 : encode(authorization_token, base64);
579 :
580 0 : set_header(
581 : g_name_edhttp_field_authorization
582 0 : , g_name_edhttp_param_basic_authorization + (' ' + base64));
583 0 : }
584 :
585 :
586 0 : void http_request::set_data(std::string const & data)
587 : {
588 0 : if(f_has_post || f_has_body)
589 : {
590 0 : throw edhttp_client_server_logic_error("you cannot use set_post(), set_data(), and set_body() on the same http_request object");
591 : }
592 :
593 0 : f_body = data;
594 :
595 0 : f_has_data = true;
596 0 : }
597 :
598 :
599 0 : void http_request::set_body(std::string const & body)
600 : {
601 0 : if(f_has_post || f_has_data)
602 : {
603 0 : throw edhttp_client_server_logic_error("you cannot use set_post(), set_data(), and set_body() on the same http_request object");
604 : }
605 :
606 0 : f_body = body;
607 :
608 0 : f_has_body = true;
609 0 : }
610 :
611 :
612 0 : std::string http_response::get_original_header() const
613 : {
614 0 : return f_original_header;
615 : }
616 :
617 :
618 0 : http_response::protocol_t http_response::get_protocol() const
619 : {
620 0 : return f_protocol;
621 : }
622 :
623 :
624 0 : int http_response::get_response_code() const
625 : {
626 0 : return f_response_code;
627 : }
628 :
629 :
630 0 : std::string http_response::get_http_message() const
631 : {
632 0 : return f_http_message;
633 : }
634 :
635 :
636 0 : bool http_response::has_header(std::string const & name) const
637 : {
638 0 : return f_header.find(name) != f_header.end();
639 : }
640 :
641 :
642 0 : std::string http_response::get_header(std::string const & name) const
643 : {
644 0 : return f_header.at(name);
645 : }
646 :
647 :
648 0 : std::string http_response::get_response() const
649 : {
650 0 : return f_response;
651 : }
652 :
653 :
654 0 : void http_response::append_original_header(std::string const & header)
655 : {
656 0 : f_original_header += header;
657 0 : f_original_header += "\r\n";
658 0 : }
659 :
660 :
661 0 : void http_response::set_protocol(protocol_t protocol)
662 : {
663 0 : f_protocol = protocol;
664 0 : }
665 :
666 :
667 0 : void http_response::set_response_code(int code)
668 : {
669 0 : f_response_code = code;
670 0 : }
671 :
672 :
673 0 : void http_response::set_http_message(std::string const & message)
674 : {
675 0 : f_http_message = message;
676 0 : }
677 :
678 :
679 0 : void http_response::set_header(std::string const& name, std::string const & value)
680 : {
681 0 : f_header[name] = value;
682 0 : }
683 :
684 :
685 0 : void http_response::set_response(std::string const & response)
686 : {
687 0 : f_response = response;
688 0 : }
689 :
690 :
691 0 : void http_response::read_response(ed::tcp_bio_client::pointer_t connection)
692 : {
693 0 : struct reader
694 : {
695 0 : reader(http_response * response, ed::tcp_bio_client::pointer_t connection)
696 0 : : f_response(response)
697 0 : , f_connection(connection)
698 : {
699 0 : }
700 :
701 : reader(reader const & rhs) = delete;
702 : reader & operator = (reader const & rhs) = delete;
703 :
704 0 : void process()
705 : {
706 0 : read_protocol();
707 0 : read_header();
708 0 : read_body();
709 0 : }
710 :
711 0 : int read_line(std::string& line)
712 : {
713 0 : int r(f_connection->read_line(line));
714 0 : if(r >= 1)
715 : {
716 0 : if(*line.rbegin() == '\r')
717 : {
718 : // remove the '\r' if present (should be)
719 0 : line.erase(line.end() - 1);
720 0 : --r;
721 : }
722 : }
723 0 : return r;
724 : }
725 :
726 0 : void read_protocol()
727 : {
728 : // first check that the protocol is HTTP and get the answer code
729 0 : SNAP_LOG_TRACE
730 : << "*** read the protocol line"
731 : << SNAP_LOG_SEND;
732 0 : std::string protocol;
733 0 : int const r(read_line(protocol));
734 0 : if(r < 0)
735 : {
736 0 : SNAP_LOG_ERROR
737 : << "read I/O error while reading HTTP protocol in response"
738 : << SNAP_LOG_SEND;
739 0 : throw client_io_error("read I/O error while reading HTTP protocol in response");
740 : }
741 0 : f_response->append_original_header(protocol);
742 :
743 0 : SNAP_LOG_TRACE
744 : << "*** got protocol: "
745 : << protocol
746 : << SNAP_LOG_SEND;
747 0 : char const *p(protocol.c_str());
748 0 : if(strncmp(p, "HTTP/1.0 ", 9) == 0)
749 : {
750 0 : f_response->set_protocol(protocol_t::HTTP_1_0);
751 0 : p += 9; // skip protocol
752 : }
753 0 : else if(strncmp(p, "HTTP/1.1 ", 9) == 0)
754 : {
755 0 : f_response->set_protocol(protocol_t::HTTP_1_1);
756 0 : p += 9; // skip protocol
757 : }
758 : else
759 : {
760 : // HTTP/2 is in the making, but it does not seem to
761 : // be officially out yet...
762 0 : SNAP_LOG_ERROR
763 : << "unknown protocol \""
764 : << protocol
765 : << "\", we only accept HTTP/1.0 and HTTP/1.1 at this time."
766 : << SNAP_LOG_SEND;
767 0 : throw client_io_error("read I/O error while reading HTTP protocol in response");
768 : }
769 : // skip any extra spaces (should be none)
770 0 : for(; isspace(*p); ++p);
771 0 : int count(0);
772 0 : int response_code(0);
773 0 : for(; *p >= '0' && *p <= '9'; ++p, ++count)
774 : {
775 : // no overflow check necessary, we expect from 1 to 3 digits
776 0 : response_code = response_code * 10 + (*p - '0');
777 : }
778 0 : if(count != 3)
779 : {
780 0 : SNAP_LOG_ERROR
781 : << "unknown response code \""
782 : << protocol
783 : << "\", all response code are expected to be three digits (i.e. 200, 401, or 500)."
784 : << SNAP_LOG_SEND;
785 0 : throw client_io_error("unknown response code, expected exactly three digits");
786 : }
787 0 : f_response->set_response_code(response_code);
788 0 : SNAP_LOG_TRACE
789 0 : << "*** +---> code: "
790 : << response_code
791 : << SNAP_LOG_SEND;
792 : // skip any spaces after the code
793 0 : for(; isspace(*p); ++p);
794 0 : f_response->set_http_message(p);
795 0 : SNAP_LOG_TRACE
796 : << "*** +---> msg: "
797 : << p
798 : << SNAP_LOG_SEND;
799 0 : }
800 :
801 0 : void read_header()
802 : {
803 : for(;;)
804 : {
805 0 : std::string field;
806 0 : int const r(read_line(field));
807 0 : if(r < 0)
808 : {
809 0 : SNAP_LOG_ERROR
810 : << "read I/O error while reading header"
811 : << SNAP_LOG_SEND;
812 0 : throw client_io_error("read I/O error while reading header");
813 : }
814 0 : if(r == 0)
815 : {
816 : // found the empty line after the header
817 : // we are done reading the header then
818 0 : break;
819 : }
820 0 : f_response->append_original_header(field);
821 :
822 0 : SNAP_LOG_TRACE
823 : << "got a header field: "
824 : << field
825 : << SNAP_LOG_SEND;
826 0 : char const * f(field.c_str());
827 0 : char const * e(f);
828 0 : for(; *e != ':' && *e != '\0'; ++e);
829 0 : if(*e != ':')
830 : {
831 : // TODO: add support for long fields that continue on
832 : // the following line
833 0 : SNAP_LOG_ERROR
834 : << "invalid header, field definition does not include a colon"
835 : << SNAP_LOG_SEND;
836 0 : throw client_io_error("invalid header, field definition does not include a colon");
837 : }
838 : // get the name and make it lowercase so we can search for
839 : // it with ease (HTTP field names are case insensitive)
840 0 : std::string name(f, e - f);
841 0 : std::transform(name.begin(), name.end(), name.begin(), ::tolower);
842 :
843 : // skip the ':' and then left trimming of spaces
844 0 : for(++e; isspace(*e); ++e);
845 0 : char const * end(f + field.length());
846 0 : for(; end > e && isspace(end[-1]); --end);
847 0 : std::string const value(e, end - e);
848 :
849 0 : f_response->set_header(name, value);
850 0 : }
851 0 : }
852 :
853 0 : void read_body()
854 : {
855 0 : if(f_response->has_header(g_name_edhttp_field_content_length_lowercase))
856 : {
857 : // server sent a content-length parameter, make use of
858 : // it and do one "large" read
859 0 : std::string const length(f_response->get_header(g_name_edhttp_field_content_length_lowercase));
860 0 : int64_t content_length(0);
861 0 : for(char const * l(length.c_str()); *l != '\0'; ++l)
862 : {
863 0 : if(*l < '0' || *l > '9')
864 : {
865 0 : SNAP_LOG_ERROR
866 : << "server returned HTTP Content-Length \""
867 : << length
868 : << "\", which includes invalid characters"
869 : << SNAP_LOG_SEND;
870 0 : throw client_io_error("server returned an HTTP Content-Length which includes invalid characters");
871 : }
872 0 : content_length = content_length * 10 + (*l - '0');
873 0 : if(content_length > 0xFFFFFFFF) // TBD: should we have a much lower limit?
874 : {
875 0 : SNAP_LOG_ERROR
876 : << "server returned an HTTP Content-Length of "
877 : << length
878 : << ", which is too large"
879 : << SNAP_LOG_SEND;
880 0 : throw client_io_error("server return an HTTP Content-Length which is too large");
881 : }
882 : }
883 : // if content-length is zero, the body response is empty
884 0 : if(content_length > 0)
885 : {
886 0 : std::vector<char> buffer;
887 0 : buffer.resize(content_length);
888 0 : SNAP_LOG_TRACE
889 0 : << "reading "
890 0 : << content_length
891 : << " bytes..."
892 : << SNAP_LOG_SEND;
893 0 : int const r(f_connection->read(&buffer[0], content_length));
894 0 : if(r < 0)
895 : {
896 0 : SNAP_LOG_ERROR
897 : << "read I/O error while reading response body"
898 : << SNAP_LOG_SEND;
899 0 : throw client_io_error("read I/O error while reading response body");
900 : }
901 0 : if(r != content_length)
902 : {
903 0 : SNAP_LOG_ERROR
904 : << "read returned before the entire content buffer was read"
905 : << SNAP_LOG_SEND;
906 0 : throw client_io_error("read returned before the entire content buffer was read");
907 : }
908 0 : f_response->set_response(std::string(&buffer[0], content_length));
909 0 : SNAP_LOG_TRACE
910 : << "body ["
911 0 : << f_response->get_response()
912 : << "]..."
913 : << SNAP_LOG_SEND;
914 : }
915 : }
916 : else
917 : {
918 : // server did not specify the content-length, this means
919 : // the request ends when the connection gets closed
920 0 : char buffer[BUFSIZ];
921 0 : std::string response;
922 : for(;;)
923 : {
924 0 : int const r(f_connection->read(buffer, BUFSIZ));
925 0 : if(r < 0)
926 : {
927 0 : SNAP_LOG_ERROR
928 : << "read I/O error while reading response body"
929 : << SNAP_LOG_SEND;
930 0 : throw client_io_error("read I/O error while reading response body");
931 : }
932 0 : response += std::string(buffer, r);
933 0 : }
934 : f_response->set_response(response);
935 : }
936 0 : }
937 :
938 : http_response * f_response = nullptr;
939 : ed::tcp_bio_client::pointer_t f_connection = ed::tcp_bio_client::pointer_t();
940 0 : } r(this, connection);
941 :
942 0 : r.process();
943 0 : }
944 :
945 :
946 0 : bool http_client::get_keep_alive() const
947 : {
948 0 : return f_keep_alive;
949 : }
950 :
951 :
952 0 : void http_client::set_keep_alive(bool keep_alive)
953 : {
954 0 : f_keep_alive = keep_alive;
955 0 : }
956 :
957 :
958 0 : http_response::pointer_t http_client::send_request(http_request const & request)
959 : {
960 : // we can keep a connection alive, but the host and port cannot
961 : // change between calls... if you need to make such changes, you
962 : // may want to consider using another http_client object, otherwise
963 : // we disconnect the previous connection and reconnect with a new one
964 : //
965 : // TBD: test cache with the addresses instead?
966 : //
967 0 : int const port(request.get_port());
968 0 : std::string const host(request.get_host());
969 0 : if(f_connection != nullptr
970 0 : && (f_host != host || f_port != port))
971 : {
972 0 : f_connection.reset();
973 : }
974 :
975 : // if we have no connection, create a new one
976 0 : if(f_connection == nullptr)
977 : {
978 : // TODO: allow user to specify the security instead of using the port
979 0 : addr::addr_range::vector_t address_ranges(request.get_address_ranges());
980 0 : if(address_ranges.empty())
981 : {
982 0 : SNAP_LOG_ERROR
983 : << "no addresses available for client to connect."
984 : << SNAP_LOG_SEND;
985 0 : throw client_no_addresses("no addresses available for client to connect.");
986 : }
987 :
988 : // TODO: attempt connecting to any of the offered addresses
989 : //
990 0 : for(auto & r : address_ranges)
991 : {
992 : try
993 : {
994 0 : f_connection = std::make_shared<ed::tcp_bio_client>(
995 : r.get_from(),
996 0 : r.get_from().get_port() == 443
997 0 : ? ed::tcp_bio_client::mode_t::MODE_ALWAYS_SECURE
998 0 : : ed::tcp_bio_client::mode_t::MODE_PLAIN);
999 :
1000 : // we successfully connected, so exit the loop
1001 0 : break;
1002 : }
1003 0 : catch(ed::event_dispatcher_failed_connecting const & e)
1004 : {
1005 : // try again on a connection error
1006 : }
1007 : }
1008 :
1009 0 : f_host = host;
1010 0 : f_port = port;
1011 : }
1012 :
1013 : // build and send the request to the server
1014 0 : std::string const data(request.get_request(f_keep_alive));
1015 : //std::cerr << "***\n*** request = [" << data << "]\n***\n";
1016 0 : f_connection->write(data.c_str(), data.length());
1017 :
1018 : // create a response and read the server's answer in that object
1019 0 : http_response::pointer_t p(new http_response);
1020 0 : p->read_response(f_connection);
1021 :
1022 : // keep connection for further calls?
1023 0 : if(!f_keep_alive
1024 0 : || p->get_header("connection") == "close")
1025 : {
1026 0 : f_connection.reset();
1027 : }
1028 :
1029 0 : return p;
1030 : }
1031 :
1032 :
1033 :
1034 6 : } // namespace edhttp
1035 : // vim: ts=4 sw=4 et
|