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