LCOV - code coverage report
Current view: top level - snapwebsites - http_client_server.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 313 0.3 %
Date: 2019-12-15 17:13:15 Functions: 2 45 4.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // HTTP Client & Server -- classes to ease handling HTTP protocol
       2             : // Copyright (c) 2012-2019  Made to Order Software Corp.  All Rights Reserved
       3             : //
       4             : // https://snapwebsites.org/
       5             : // contact@m2osw.com
       6             : //
       7             : // This program is free software; you can redistribute it and/or modify
       8             : // it under the terms of the GNU General Public License as published by
       9             : // the Free Software Foundation; either version 2 of the License, or
      10             : // (at your option) any later version.
      11             : //
      12             : // This program is distributed in the hope that it will be useful,
      13             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             : // GNU General Public License for more details.
      16             : //
      17             : // You should have received a copy of the GNU General Public License
      18             : // along with this program; if not, write to the Free Software
      19             : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
      20             : 
      21             : 
      22             : // self
      23             : //
      24             : #include "snapwebsites/http_client_server.h"
      25             : 
      26             : 
      27             : // snapwebsites
      28             : //
      29             : #include "snapwebsites/log.h"
      30             : #include "snapwebsites/snap_uri.h"
      31             : #include "snapwebsites/snapwebsites.h"
      32             : 
      33             : 
      34             : // snapdev lib
      35             : //
      36             : #include <snapdev/not_reached.h>
      37             : 
      38             : 
      39             : // C++ lib
      40             : //
      41             : #include <algorithm>
      42             : #include <iostream>
      43             : #include <sstream>
      44             : 
      45             : 
      46             : // last include
      47             : //
      48             : #include <snapdev/poison.h>
      49             : 
      50             : 
      51             : namespace http_client_server
      52             : {
      53             : 
      54             : 
      55             : namespace
      56             : {
      57             : 
      58             : char const g_base64[] =
      59             : {
      60             :     // 8x8 characters
      61             :     'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
      62             :     'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
      63             :     'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
      64             :     'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
      65             :     'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
      66             :     'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
      67             :     'w', 'x', 'y', 'z', '0', '1', '2', '3',
      68             :     '4', '5', '6', '7', '8', '9', '+', '/'
      69             : };
      70             : 
      71             : }
      72             : // no name namespace
      73             : 
      74             : 
      75             : 
      76           0 : std::string http_request::get_host() const
      77             : {
      78           0 :     return f_host;
      79             : }
      80             : 
      81             : 
      82           0 : int http_request::get_port() const
      83             : {
      84           0 :     return f_port;
      85             : }
      86             : 
      87             : 
      88           0 : std::string http_request::get_command() const
      89             : {
      90           0 :     return f_command;
      91             : }
      92             : 
      93             : 
      94           0 : std::string http_request::get_path() const
      95             : {
      96           0 :     return f_path;
      97             : }
      98             : 
      99             : 
     100           0 : std::string http_request::get_header(std::string const & name) const
     101             : {
     102           0 :     if(f_headers.find(name) == f_headers.end())
     103             :     {
     104           0 :         return "";
     105             :     }
     106           0 :     return f_headers.at(name);
     107             : }
     108             : 
     109             : 
     110           0 : std::string http_request::get_post(std::string const & name) const
     111             : {
     112           0 :     if(f_post.find(name) == f_post.end())
     113             :     {
     114           0 :         return "";
     115             :     }
     116           0 :     return f_post.at(name);
     117             : }
     118             : 
     119             : 
     120           0 : std::string http_request::get_body() const
     121             : {
     122           0 :     return f_body;
     123             : }
     124             : 
     125             : 
     126           0 : std::string http_request::get_request(bool keep_alive) const
     127             : {
     128           0 :     std::stringstream request;
     129             : 
     130             :     // first we generate the body, that way we define its size
     131             :     // and also the content type in case of a POST
     132             : 
     133             :     // we will get a copy of the body as required because this
     134             :     // function is constant and we do not want to modify f_body
     135           0 :     std::string body;
     136           0 :     std::string content_type;
     137             : 
     138           0 :     if(f_has_attachment)
     139             :     {
     140           0 :         request << (f_command.empty() ? "POST" : f_command.c_str())
     141           0 :                 << " " << f_path << " HTTP/1.1\r\n";
     142             : 
     143           0 :         throw http_client_server_logic_error("http_client_server.cpp:get_request(): attachments not supported yet");
     144             :     }
     145           0 :     else if(f_has_post)
     146             :     {
     147             :         // TODO: support the case where the post variables are passed using
     148             :         //       a GET and a query string
     149           0 :         request << (f_command.empty() ? "POST" : f_command.c_str())
     150           0 :                 << " " << f_path << " HTTP/1.1\r\n";
     151           0 :         content_type = "application/x-www-form-urlencoded";
     152             : 
     153           0 :         body = "";
     154           0 :         for(auto it(f_post.begin()); it != f_post.end(); ++it)
     155             :         {
     156           0 :             if(!body.empty())
     157             :             {
     158             :                 // separate parameters by ampersands
     159           0 :                 body += "&";
     160             :             }
     161             :             // TODO: escape & and such
     162           0 :             body += it->first + "=" + it->second;
     163             :         }
     164             :     }
     165           0 :     else if(f_has_data)
     166             :     {
     167           0 :         request << (f_command.empty() ? "POST" : f_command.c_str())
     168           0 :                 << " " << f_path << " HTTP/1.1\r\n";
     169           0 :         body = f_body;
     170             :     }
     171           0 :     else if(f_has_body)
     172             :     {
     173           0 :         request << (f_command.empty() ? "GET" : f_command.c_str())
     174           0 :                 << " " << f_path << " HTTP/1.1\r\n";
     175           0 :         body = f_body;
     176             :     }
     177             :     else
     178             :     {
     179           0 :         request << (f_command.empty() ? "GET" : f_command.c_str())
     180           0 :                 << " " << f_path << " HTTP/1.1\r\n";
     181             :         // body is empty by default
     182             :         //body = "";
     183             :     }
     184             : 
     185             :     // place Host first because some servers are that stupid
     186           0 :     request << "Host: " << f_host << "\r\n";
     187             : 
     188           0 :     bool found_user_agent(false);
     189           0 :     for(auto it(f_headers.begin()); it != f_headers.end(); ++it)
     190             :     {
     191             :         // make sure we do not output the following fields which are
     192             :         // managed by our code instead:
     193             :         //
     194             :         //      Content-Length
     195             :         //
     196           0 :         std::string name(it->first);
     197           0 :         std::transform(name.begin(), name.end(), name.begin(), ::tolower);
     198           0 :         if((content_type.empty() || name != "content-type")
     199           0 :         && name != "content-length"
     200           0 :         && name != "host"
     201           0 :         && name != "connection")
     202             :         {
     203           0 :             if(name == "user-agent")
     204             :             {
     205           0 :                 found_user_agent = true;
     206             :             }
     207           0 :             request << it->first
     208             :                     << ": "
     209           0 :                     << it->second
     210           0 :                     << "\r\n";
     211             :         }
     212             :     }
     213             : 
     214             :     // forcing the type? (generally doing so with POSTs)
     215           0 :     if(!content_type.empty())
     216             :     {
     217           0 :         request << "Content-Type: " << content_type << "\r\n";
     218             :     }
     219           0 :     if(!found_user_agent)
     220             :     {
     221           0 :         request << "User-Agent: snapwebsites/" SNAPWEBSITES_VERSION_STRING "\r\n";
     222             :     }
     223             : 
     224             :     // force the connection valid to what the programmer asked (keep-alive by
     225             :     // default though)
     226             :     //
     227             :     // WARNING: according to HTTP/1.1, servers only expect "close" and not
     228             :     //          "keep-alive"; however, it looks like many implementations
     229             :     //          understand both (there is also an "upgrade" which we do not
     230             :     //          support)
     231           0 :     request << "Connection: " << (keep_alive ? "keep-alive" : "close") << "\r\n";
     232             : 
     233             :     // end the list with the fields we control:
     234             :     //
     235             :     // Content-Length is the size of the body
     236           0 :     request << "Content-Length: " << body.length() << "\r\n\r\n";
     237             : 
     238             :     // TBD: will this work if 'body' includes a '\0'?
     239           0 :     request << body;
     240             : 
     241           0 :     return request.str();
     242             : }
     243             : 
     244             : 
     245             : /** \brief Set the host, port, and path at once.
     246             :  *
     247             :  * HTTP accepts full URIs in the GET, POST, etc. line
     248             :  * so the following would be valid:
     249             :  *
     250             :  *    GET https://snapwebsites.org/some/path?a=view HTTP/1.1
     251             :  *
     252             :  * However, we break it down in a few separate parts instead, because
     253             :  * (a) we need the host to connect to the server, (b) we need the port
     254             :  * to connect to the server:
     255             :  *
     256             :  * 1. Remove protocol, this defines whether we use plain text (http)
     257             :  *    or encryption (https/ssl)
     258             :  * 2. Get the port, if not specified after the domain, use the default
     259             :  *    of the specified URI protocol
     260             :  * 3. Domain name is moved to the 'Host: ...' header
     261             :  * 4. Path and query string are kept as is
     262             :  *
     263             :  * So the example above changes to:
     264             :  *
     265             :  *    GET /some/path?a=view HTTP/1.1
     266             :  *    Host: snapwebsites.org
     267             :  *
     268             :  * We use a plain text connection (http:) and the port is the default
     269             :  * port for the HTTP protocol (80). That information does not appear
     270             :  * in the HTTP header.
     271             :  *
     272             :  * \param[in] uri  The URI to save in this HTTP request.
     273             :  */
     274           0 : void http_request::set_uri(std::string const & uri)
     275             : {
     276           0 :     snap::snap_uri u(QString::fromUtf8(uri.c_str()));
     277           0 :     f_host = u.full_domain().toUtf8().data();
     278           0 :     f_port = u.get_port();
     279             : 
     280             :     // use set_path() to make sure we get an absolute path
     281             :     // (which is not the case by default)
     282           0 :     set_path(u.path().toUtf8().data());
     283             : 
     284             :     // keep the query string parameters if any are defined
     285           0 :     QString const q(u.query_string());
     286           0 :     if(!q.isEmpty())
     287             :     {
     288           0 :         f_path += "?";
     289           0 :         f_path += q.toUtf8().data();
     290             :     }
     291           0 : }
     292             : 
     293             : 
     294           0 : void http_request::set_host(std::string const & host)
     295             : {
     296           0 :     f_host = host;
     297           0 : }
     298             : 
     299             : 
     300           0 : void http_request::set_port(int port)
     301             : {
     302             :     // port will be verified by the tcp_client_server code, so no need to
     303             :     // do that again here
     304           0 :     f_port = port;
     305           0 : }
     306             : 
     307             : 
     308           0 : void http_request::set_command(std::string const & command)
     309             : {
     310           0 :     f_command = command;
     311           0 : }
     312             : 
     313             : 
     314           0 : void http_request::set_path(std::string const & path)
     315             : {
     316             :     // TODO: better verify path validity
     317           0 :     if(path.empty())
     318             :     {
     319           0 :         f_path = "/";
     320             :     }
     321           0 :     else if(path[0] == '/')
     322             :     {
     323           0 :         f_path = path;
     324             :     }
     325             :     else
     326             :     {
     327           0 :         f_path = "/" + path;
     328             :     }
     329           0 : }
     330             : 
     331             : 
     332           0 : void http_request::set_header(std::string const & name, std::string const & value)
     333             : {
     334             :     // TODO: verify that the header name is compatible/valid
     335             :     // TODO: for known names, verify that the value is compatible/valid
     336             :     // TODO: verify the value in various other ways
     337           0 :     if(value.empty())
     338             :     {
     339             :         // remove headers if defined
     340           0 :         auto h(f_headers.find(value));
     341           0 :         if(h != f_headers.end())
     342             :         {
     343           0 :             f_headers.erase(h);
     344             :         }
     345             :     }
     346             :     else
     347             :     {
     348             :         // add header, overwrite if already defined
     349           0 :         f_headers[name] = value;
     350             :     }
     351           0 : }
     352             : 
     353             : 
     354           0 : void http_request::set_post(std::string const & name, std::string const & value)
     355             : {
     356           0 :     if(f_has_body || f_has_data)
     357             :     {
     358           0 :         throw http_client_server_logic_error("you cannot use set_body(), set_data(), and set_post() on the same http_request object");
     359             :     }
     360             : 
     361             :     // TODO: verify that the name is a valid name for a post variable
     362           0 :     f_post[name] = value;
     363             : 
     364           0 :     f_has_post = true;
     365           0 : }
     366             : 
     367             : 
     368           0 : void http_request::set_basic_auth(std::string const & username, std::string const & secret)
     369             : {
     370           0 :     auto encode = [](std::string const & in, std::string & out)
     371             :     {
     372             :         // reset output (just in case)
     373           0 :         out.clear();
     374             : 
     375             :         // WARNING: following algorithm does NOT take any line length
     376             :         //          in account; and it is deadly well optimized
     377           0 :         unsigned char const *s(reinterpret_cast<unsigned char const *>(in.c_str()));
     378           0 :         while(*s != '\0')
     379             :         {
     380             :             // get 1 to 3 characters of input
     381           0 :             out += g_base64[s[0] >> 2]; // & 0x3F not required
     382           0 :             ++s;
     383           0 :             if(s[0] != '\0')
     384             :             {
     385           0 :                 out += g_base64[((s[-1] << 4) & 0x30) | ((s[0] >> 4) & 0x0F)];
     386           0 :                 ++s;
     387           0 :                 if(s[0] != '\0')
     388             :                 {
     389             :                     // 24 bits of input uses 4 base64 characters
     390           0 :                     out += g_base64[((s[-1] << 2) & 0x3C) | ((s[0] >> 6) & 0x03)];
     391           0 :                     out += g_base64[s[0] & 0x3F];
     392           0 :                     s++;
     393             :                 }
     394             :                 else
     395             :                 {
     396             :                     // 16 bits of input uses 3 base64 characters + 1 pad
     397           0 :                     out += g_base64[(s[-1] << 2) & 0x3C];
     398           0 :                     out += '=';
     399           0 :                     break;
     400             :                 }
     401             :             }
     402             :             else
     403             :             {
     404             :                 // 8 bits of input uses 2 base64 characters + 2 pads
     405           0 :                 out += g_base64[(s[-1] << 4) & 0x30];
     406           0 :                 out += "==";
     407           0 :                 break;
     408             :             }
     409             :         }
     410           0 :     };
     411             : 
     412           0 :     std::string const authorization_token(username + ":" + secret);
     413           0 :     std::string base64;
     414           0 :     encode(authorization_token, base64);
     415             : 
     416           0 :     set_header("Authorization", "Basic " + base64);
     417           0 : }
     418             : 
     419             : 
     420           0 : void http_request::set_data(std::string const & data)
     421             : {
     422           0 :     if(f_has_post || f_has_body)
     423             :     {
     424           0 :         throw http_client_server_logic_error("you cannot use set_post(), set_data(), and set_body() on the same http_request object");
     425             :     }
     426             : 
     427           0 :     f_body = data;
     428             : 
     429           0 :     f_has_data = true;
     430           0 : }
     431             : 
     432             : 
     433           0 : void http_request::set_body(std::string const & body)
     434             : {
     435           0 :     if(f_has_post || f_has_data)
     436             :     {
     437           0 :         throw http_client_server_logic_error("you cannot use set_post(), set_data(), and set_body() on the same http_request object");
     438             :     }
     439             : 
     440           0 :     f_body = body;
     441             : 
     442           0 :     f_has_body = true;
     443           0 : }
     444             : 
     445             : 
     446           0 : std::string http_response::get_original_header() const
     447             : {
     448           0 :     return f_original_header;
     449             : }
     450             : 
     451             : 
     452           0 : http_response::protocol_t http_response::get_protocol() const
     453             : {
     454           0 :     return f_protocol;
     455             : }
     456             : 
     457             : 
     458           0 : int http_response::get_response_code() const
     459             : {
     460           0 :     return f_response_code;
     461             : }
     462             : 
     463             : 
     464           0 : std::string http_response::get_http_message() const
     465             : {
     466           0 :     return f_http_message;
     467             : }
     468             : 
     469             : 
     470           0 : bool http_response::has_header(std::string const & name) const
     471             : {
     472           0 :     return f_header.find(name) != f_header.end();
     473             : }
     474             : 
     475             : 
     476           0 : std::string http_response::get_header(std::string const & name) const
     477             : {
     478           0 :     return f_header.at(name);
     479             : }
     480             : 
     481             : 
     482           0 : std::string http_response::get_response() const
     483             : {
     484           0 :     return f_response;
     485             : }
     486             : 
     487             : 
     488           0 : void http_response::append_original_header(std::string const & header)
     489             : {
     490           0 :     f_original_header += header;
     491           0 :     f_original_header += "\r\n";
     492           0 : }
     493             : 
     494             : 
     495           0 : void http_response::set_protocol(protocol_t protocol)
     496             : {
     497           0 :     f_protocol = protocol;
     498           0 : }
     499             : 
     500             : 
     501           0 : void http_response::set_response_code(int code)
     502             : {
     503           0 :     f_response_code = code;
     504           0 : }
     505             : 
     506             : 
     507           0 : void http_response::set_http_message(std::string const & message)
     508             : {
     509           0 :     f_http_message = message;
     510           0 : }
     511             : 
     512             : 
     513           0 : void http_response::set_header(std::string const& name, std::string const & value)
     514             : {
     515           0 :     f_header[name] = value;
     516           0 : }
     517             : 
     518             : 
     519           0 : void http_response::set_response(std::string const & response)
     520             : {
     521           0 :     f_response = response;
     522           0 : }
     523             : 
     524             : 
     525           0 : void http_response::read_response(tcp_client_server::bio_client::pointer_t connection)
     526             : {
     527           0 :     struct reader
     528             :     {
     529           0 :         reader(http_response * response, tcp_client_server::bio_client::pointer_t connection)
     530           0 :             : f_response(response)
     531           0 :             , f_connection(connection)
     532             :         {
     533           0 :         }
     534             : 
     535             :         reader(reader const & rhs) = delete;
     536             :         reader & operator = (reader const & rhs) = delete;
     537             : 
     538           0 :         void process()
     539             :         {
     540           0 :             read_protocol();
     541           0 :             read_header();
     542           0 :             read_body();
     543           0 :         }
     544             : 
     545           0 :         int read_line(std::string& line)
     546             :         {
     547           0 :             int r(f_connection->read_line(line));
     548           0 :             if(r >= 1)
     549             :             {
     550           0 :                 if(*line.rbegin() == '\r')
     551             :                 {
     552             :                     // remove the '\r' if present (should be)
     553           0 :                     line.erase(line.end() - 1);
     554           0 :                     --r;
     555             :                 }
     556             :             }
     557           0 :             return r;
     558             :         }
     559             : 
     560           0 :         void read_protocol()
     561             :         {
     562             :             // first check that the protocol is HTTP and get the answer code
     563           0 : SNAP_LOG_TRACE("*** read the protocol line");
     564           0 :             std::string protocol;
     565           0 :             int const r(read_line(protocol));
     566           0 :             if(r < 0)
     567             :             {
     568           0 :                 SNAP_LOG_ERROR("read I/O error while reading HTTP protocol in response");
     569           0 :                 throw http_client_exception_io_error("read I/O error while reading HTTP protocol in response");
     570             :             }
     571           0 :             f_response->append_original_header(protocol);
     572             : 
     573           0 : SNAP_LOG_TRACE("*** got protocol: ")(protocol);
     574           0 :             char const *p(protocol.c_str());
     575           0 :             if(strncmp(p, "HTTP/1.0 ", 9) == 0)
     576             :             {
     577           0 :                 f_response->set_protocol(protocol_t::HTTP_1_0);
     578           0 :                 p += 9; // skip protocol
     579             :             }
     580           0 :             else if(strncmp(p, "HTTP/1.1 ", 9) == 0)
     581             :             {
     582           0 :                 f_response->set_protocol(protocol_t::HTTP_1_1);
     583           0 :                 p += 9; // skip protocol
     584             :             }
     585             :             else
     586             :             {
     587             :                 // HTTP/2 is in the making, but it does not seem to
     588             :                 // be officially out yet...
     589           0 :                 SNAP_LOG_ERROR("unknown protocol \"")(protocol)("\", we only accept HTTP/1.0 and HTTP/1.1 at this time.");
     590           0 :                 throw http_client_exception_io_error("read I/O error while reading HTTP protocol in response");
     591             :             }
     592             :             // skip any extra spaces (should be none)
     593           0 :             for(; isspace(*p); ++p);
     594           0 :             int count(0);
     595           0 :             int response_code(0);
     596           0 :             for(; *p >= '0' && *p <= '9'; ++p, ++count)
     597             :             {
     598             :                 // no overflow check necessary, we expect from 1 to 3 digits
     599           0 :                 response_code = response_code * 10 + (*p - '0');
     600             :             }
     601           0 :             if(count != 3)
     602             :             {
     603           0 :                 SNAP_LOG_ERROR("unknown response code \"")(protocol)("\", all response code are expected to be three digits (i.e. 200, 401, or 500).");
     604           0 :                 throw http_client_exception_io_error("unknown response code, expected exactly three digits");
     605             :             }
     606           0 :             f_response->set_response_code(response_code);
     607           0 : SNAP_LOG_TRACE("***   +---> code: ")(response_code);
     608             :             // skip any spaces after the code
     609           0 :             for(; isspace(*p); ++p);
     610           0 :             f_response->set_http_message(p);
     611           0 : SNAP_LOG_TRACE("***   +---> msg: ")(p);
     612           0 :         }
     613             : 
     614           0 :         void read_header()
     615             :         {
     616             :             for(;;)
     617             :             {
     618           0 :                 std::string field;
     619           0 :                 int const r(read_line(field));
     620           0 :                 if(r < 0)
     621             :                 {
     622           0 :                     SNAP_LOG_ERROR("read I/O error while reading header");
     623           0 :                     throw http_client_exception_io_error("read I/O error while reading header");
     624             :                 }
     625           0 :                 if(r == 0)
     626             :                 {
     627             :                     // found the empty line after the header
     628             :                     // we are done reading the header then
     629           0 :                     break;
     630             :                 }
     631           0 :                 f_response->append_original_header(field);
     632             : 
     633           0 : SNAP_LOG_TRACE("got a header field: ")(field);
     634           0 :                 char const * f(field.c_str());
     635           0 :                 char const * e(f);
     636           0 :                 for(; *e != ':' && *e != '\0'; ++e);
     637           0 :                 if(*e != ':')
     638             :                 {
     639             :                     // TODO: add support for long fields that continue on
     640             :                     //       the following line
     641           0 :                     SNAP_LOG_ERROR("invalid header, field definition does not include a colon");
     642           0 :                     throw http_client_exception_io_error("invalid header, field definition does not include a colon");
     643             :                 }
     644             :                 // get the name and make it lowercase so we can search for
     645             :                 // it with ease (HTTP field names are case insensitive)
     646           0 :                 std::string name(f, e - f);
     647           0 :                 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
     648             : 
     649             :                 // skip the ':' and then left trimming of spaces
     650           0 :                 for(++e; isspace(*e); ++e);
     651           0 :                 char const * end(f + field.length());
     652           0 :                 for(; end > e && isspace(end[-1]); --end);
     653           0 :                 std::string const value(e, end - e);
     654             : 
     655           0 :                 f_response->set_header(name, value);
     656           0 :             }
     657           0 :         }
     658             : 
     659           0 :         void read_body()
     660             :         {
     661           0 :             if(f_response->has_header("content-length"))
     662             :             {
     663             :                 // server sent a content-length parameter, make use of
     664             :                 // it and do one "large" read
     665           0 :                 std::string const length(f_response->get_header("content-length"));
     666           0 :                 int64_t content_length(0);
     667           0 :                 for(char const * l(length.c_str()); *l != '\0'; ++l)
     668             :                 {
     669           0 :                     if(*l < '0' || *l > '9')
     670             :                     {
     671           0 :                         SNAP_LOG_ERROR("server returned HTTP Content-Length \"")(length)("\", which includes invalid characters");
     672           0 :                         throw http_client_exception_io_error("server returned an HTTP Content-Length which includes invalid characters");
     673             :                     }
     674           0 :                     content_length = content_length * 10 + (*l - '0');
     675           0 :                     if(content_length > 0xFFFFFFFF) // TBD: should we have a much lower limit?
     676             :                     {
     677           0 :                         SNAP_LOG_ERROR("server returned an HTTP Content-Length of ")(length)(", which is too large");
     678           0 :                         throw http_client_exception_io_error("server return an HTTP Content-Length which is too large");
     679             :                     }
     680             :                 }
     681             :                 // if content-length is zero, the body response is empty
     682           0 :                 if(content_length > 0)
     683             :                 {
     684           0 :                     std::vector<char> buffer;
     685           0 :                     buffer.resize(content_length);
     686           0 : SNAP_LOG_TRACE("reading ")(content_length)(" bytes...");
     687           0 :                     int const r(f_connection->read(&buffer[0], content_length));
     688           0 :                     if(r < 0)
     689             :                     {
     690           0 :                         SNAP_LOG_ERROR("read I/O error while reading response body");
     691           0 :                         throw http_client_exception_io_error("read I/O error while reading response body");
     692             :                     }
     693           0 :                     if(r != content_length)
     694             :                     {
     695           0 :                         SNAP_LOG_ERROR("read returned before the entire content buffer was read");
     696           0 :                         throw http_client_exception_io_error("read returned before the entire content buffer was read");
     697             :                     }
     698           0 :                     f_response->set_response(std::string(&buffer[0], content_length));
     699           0 : SNAP_LOG_TRACE("body [")(f_response->get_response())("]...");
     700             :                 }
     701             :             }
     702             :             else
     703             :             {
     704             :                 // server did not specify the content-length, this means
     705             :                 // the request ends when the connection gets closed
     706             :                 char buffer[BUFSIZ];
     707           0 :                 std::string response;
     708             :                 for(;;)
     709             :                 {
     710           0 :                     int const r(f_connection->read(buffer, BUFSIZ));
     711           0 :                     if(r < 0)
     712             :                     {
     713           0 :                         SNAP_LOG_ERROR("read I/O error while reading response body");
     714           0 :                         throw http_client_exception_io_error("read I/O error while reading response body");
     715             :                     }
     716           0 :                     response += std::string(buffer, r);
     717           0 :                 }
     718             :                 f_response->set_response(response);
     719             :             }
     720           0 :         }
     721             : 
     722             :         http_response *                             f_response = nullptr;
     723             :         tcp_client_server::bio_client::pointer_t    f_connection = tcp_client_server::bio_client::pointer_t();
     724           0 :     } r(this, connection);
     725             : 
     726           0 :     r.process();
     727           0 : }
     728             : 
     729             : 
     730           0 : bool http_client::get_keep_alive() const
     731             : {
     732           0 :     return f_keep_alive;
     733             : }
     734             : 
     735             : 
     736           0 : void http_client::set_keep_alive(bool keep_alive)
     737             : {
     738           0 :     f_keep_alive = keep_alive;
     739           0 : }
     740             : 
     741             : 
     742           0 : http_response::pointer_t http_client::send_request(http_request const & request)
     743             : {
     744             :     // we can keep a connection alive, but the host and port cannot
     745             :     // change between calls... if you need to make such changes, you
     746             :     // may want to consider using another http_client object, otherwise
     747             :     // we disconnect the previous connection and reconnect with a new one
     748           0 :     int const port(request.get_port());
     749           0 :     std::string const host(request.get_host());
     750           0 :     if(f_connection
     751           0 :     && (f_host != host || f_port != port))
     752             :     {
     753           0 :         f_connection.reset();
     754             :     }
     755             : 
     756             :     // if we have no connection, create a new one
     757           0 :     if(!f_connection)
     758             :     {
     759             :         // TODO: allow user to specify the security instead of using the port
     760           0 :         f_connection.reset(new tcp_client_server::bio_client(
     761             :                     host,
     762             :                     port,
     763             :                     port == 443 ? tcp_client_server::bio_client::mode_t::MODE_ALWAYS_SECURE
     764           0 :                                 : tcp_client_server::bio_client::mode_t::MODE_PLAIN));
     765           0 :         f_host = host;
     766           0 :         f_port = port;
     767             :     }
     768             : 
     769             :     // build and send the request to the server
     770           0 :     std::string const data(request.get_request(f_keep_alive));
     771             : //std::cerr << "***\n*** request = [" << data << "]\n***\n";
     772           0 :     f_connection->write(data.c_str(), data.length());
     773             : 
     774             :     // create a response and read the server's answer in that object
     775           0 :     http_response::pointer_t p(new http_response);
     776           0 :     p->read_response(f_connection);
     777             : 
     778             :     // keep connection for further calls?
     779           0 :     if(!f_keep_alive
     780           0 :     || p->get_header("connection") == "close")
     781             :     {
     782           0 :         f_connection.reset();
     783             :     }
     784             : 
     785           0 :     return p;
     786             : }
     787             : 
     788             : 
     789             : 
     790           6 : } // namespace http_client_server
     791             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13