LCOV - code coverage report
Current view: top level - edhttp - http_client_server.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 378 0.3 %
Date: 2022-07-09 10:44:38 Functions: 2 51 3.9 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13