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-03-15 17:12:29 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/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

Generated by: LCOV version 1.13