From f21ce291aeec8f096c2980626bffbb91a3bfba1a Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 21 Aug 2013 11:59:43 +0200 Subject: [PATCH] Added many helper functions to HTTP library, optimizing and fixing support for certain cases (among which chunked encoding). --- lib/http_parser.cpp | 210 +++++++++++++++++++++++++++++++++++++++++--- lib/http_parser.h | 7 ++ 2 files changed, 206 insertions(+), 11 deletions(-) diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 460cb2b7..56259179 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -2,6 +2,7 @@ /// Holds all code for the HTTP namespace. #include "http_parser.h" +#include "timing.h" /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. /// All this constructor does is call HTTP::Parser::Clean(). @@ -45,6 +46,27 @@ std::string & HTTP::Parser::BuildRequest(){ return builder; } +/// Creates and sends a valid HTTP 1.0 or 1.1 request. +/// The request is build from internal variables set before this call is made. +/// To be precise, method, url, protocol, headers and body are used. +void HTTP::Parser::SendRequest(Socket::Connection & conn){ + /// \todo Include GET/POST variable parsing? + std::map::iterator it; + if (protocol.size() < 5 || protocol.substr(0, 4) != "HTTP"){ + protocol = "HTTP/1.0"; + } + builder = method + " " + url + " " + protocol + "\r\n"; + conn.SendNow(builder); + for (it = headers.begin(); it != headers.end(); it++){ + if (( *it).first != "" && ( *it).second != ""){ + builder = ( *it).first + ": " + ( *it).second + "\r\n"; + conn.SendNow(builder); + } + } + conn.SendNow("\r\n", 2); + conn.SendNow(body); +} + /// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending. /// The response is partly build from internal variables set before this call is made. /// To be precise, protocol, headers and body are used. @@ -70,6 +92,109 @@ std::string & HTTP::Parser::BuildResponse(std::string code, std::string message) return builder; } +/// Creates and sends a valid HTTP 1.0 or 1.1 response. +/// The response is partly build from internal variables set before this call is made. +/// To be precise, protocol, headers and body are used. +/// \param code The HTTP response code. Usually you want 200. +/// \param message The HTTP response message. Usually you want "OK". +void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::Connection & conn){ + /// \todo Include GET/POST variable parsing? + std::map::iterator it; + if (protocol.size() < 5 || protocol.substr(0, 4) != "HTTP"){ + protocol = "HTTP/1.0"; + } + builder = protocol + " " + code + " " + message + "\r\n"; + conn.SendNow(builder); + for (it = headers.begin(); it != headers.end(); it++){ + if (( *it).first != "" && ( *it).second != ""){ + if (( *it).first != "Content-Length" || ( *it).second != "0"){ + builder = ( *it).first + ": " + ( *it).second + "\r\n"; + conn.SendNow(builder); + } + } + } + conn.SendNow("\r\n", 2); +} + +/// After receiving a header with this object, this function call will: +/// - Forward the headers to the 'to' Socket::Connection. +/// - Retrieve all the body from the 'from' Socket::Connection. +/// - Forward those contents as-is to the 'to' Socket::Connection. +/// It blocks until completed or either of the connections reaches an error state. +void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to){ + SendResponse("200", "OK", to); + if (getChunks){ + int doingChunk = 0; + while (to.connected() && from.connected()){ + if (from.Received().size() || from.spool()){ + if (doingChunk){ + unsigned int toappend = from.Received().get().size(); + if (toappend > doingChunk){ + toappend = doingChunk; + to.SendNow(from.Received().get().c_str(), toappend); + from.Received().get().erase(0, toappend); + }else{ + to.SendNow(from.Received().get()); + from.Received().get().clear(); + } + doingChunk -= toappend; + }else{ + //Make sure the received data ends in a newline (\n). + if ( *(from.Received().get().rbegin()) != '\n'){ + if (from.Received().size() > 1){ + std::string tmp = from.Received().get(); + from.Received().get().clear(); + from.Received().get().insert(0, tmp); + }else{ + Util::sleep(100); + continue; + } + } + //forward the size and any empty lines + to.SendNow(from.Received().get()); + + std::string tmpA = from.Received().get().substr(0, from.Received().get().size() - 1); + while (tmpA.find('\r') != std::string::npos){ + tmpA.erase(tmpA.find('\r')); + } + unsigned int chunkLen = 0; + if ( !tmpA.empty()){ + for (int i = 0; i < tmpA.size(); ++i){ + chunkLen = (chunkLen << 4) | unhex(tmpA[i]); + } + if (chunkLen == 0){ + getChunks = false; + to.SendNow("\r\n", 2); + return; + } + doingChunk = chunkLen; + } + from.Received().get().clear(); + } + }else{ + Util::sleep(100); + } + } + }else{ + unsigned int bodyLen = length; + while (bodyLen > 0 && to.connected() && from.connected()){ + if (from.Received().size() || from.spool()){ + if (from.Received().get().size() <= bodyLen){ + to.SendNow(from.Received().get()); + bodyLen -= from.Received().get().size(); + from.Received().get().clear(); + }else{ + to.SendNow(from.Received().get().c_str(), bodyLen); + from.Received().get().erase(0, bodyLen); + bodyLen = 0; + } + }else{ + Util::sleep(100); + } + } + } +} + /// Trims any whitespace at the front or back of the string. /// Used when getting/setting headers. /// \param s The string to trim. The string itself will be changed, not returned. @@ -142,15 +267,34 @@ void HTTP::Parser::SetVar(std::string i, std::string v){ } } +/// Attempt to read a whole HTTP request or response from a Socket::Connection. +/// If a whole request could be read, it is removed from the front of the socket buffer and true returned. +/// If not, as much as can be interpreted is removed and false returned. +/// \param conn The socket to read from. +/// \return True if a whole request or response was read, false otherwise. +bool HTTP::Parser::Read(Socket::Connection & conn){ + //Make sure the received data ends in a newline (\n). + if ( *(conn.Received().get().rbegin()) != '\n'){ + if (conn.Received().size() > 1){ + std::string tmp = conn.Received().get(); + conn.Received().get().clear(); + conn.Received().get().insert(0, tmp); + }else{ + return false; + } + } + return parse(conn.Received().get()); +} //HTTPReader::Read + /// Attempt to read a whole HTTP request or response from a std::string buffer. -/// If a whole request could be read, it is removed from the front of the given buffer. +/// If a whole request could be read, it is removed from the front of the given buffer and true returned. +/// If not, as much as can be interpreted is removed and false returned. /// \param strbuf The buffer to read from. /// \return True if a whole request or response was read, false otherwise. bool HTTP::Parser::Read(std::string & strbuf){ return parse(strbuf); } //HTTPReader::Read -#include /// Attempt to read a whole HTTP response or request from a data buffer. /// If succesful, fills its own fields with the proper data and removes the response/request /// from the data buffer. @@ -177,18 +321,34 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){ seenReq = true; f = tmpA.find(' '); if (f != std::string::npos){ - method = tmpA.substr(0, f); - tmpA.erase(0, f + 1); - f = tmpA.find(' '); - if (f != std::string::npos){ - url = tmpA.substr(0, f); + if (tmpA.substr(0, 4) == "HTTP"){ + protocol = tmpA.substr(0, f); tmpA.erase(0, f + 1); - protocol = tmpA; - if (url.find('?') != std::string::npos){ - parseVars(url.substr(url.find('?') + 1)); //parse GET variables + f = tmpA.find(' '); + if (f != std::string::npos){ + url = tmpA.substr(0, f); + tmpA.erase(0, f + 1); + method = tmpA; + if (url.find('?') != std::string::npos){ + parseVars(url.substr(url.find('?') + 1)); //parse GET variables + } + }else{ + seenReq = false; } }else{ - seenReq = false; + method = tmpA.substr(0, f); + tmpA.erase(0, f + 1); + f = tmpA.find(' '); + if (f != std::string::npos){ + url = tmpA.substr(0, f); + tmpA.erase(0, f + 1); + protocol = tmpA; + if (url.find('?') != std::string::npos){ + parseVars(url.substr(url.find('?') + 1)); //parse GET variables + } + }else{ + seenReq = false; + } } }else{ seenReq = false; @@ -234,6 +394,9 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){ } }else{ if (getChunks){ + if (headerOnly){ + return true; + } if (doingChunk){ unsigned int toappend = HTTPbuffer.size(); if (toappend > doingChunk){ @@ -321,6 +484,31 @@ void HTTP::Parser::Chunkify(std::string & bodypart){ } } +/// Converts a string to chunked format if protocol is HTTP/1.1 - does nothing otherwise. +/// \param bodypart The data to convert - will be converted in-place. +void HTTP::Parser::Chunkify(std::string & bodypart, Socket::Connection & conn){ + Chunkify(bodypart.c_str(), bodypart.size(), conn); +} + +/// Converts a string to chunked format if protocol is HTTP/1.1 - does nothing otherwise. +/// \param bodypart The data to convert - will be converted in-place. +void HTTP::Parser::Chunkify(const char * data, unsigned int size, Socket::Connection & conn){ + if (protocol == "HTTP/1.1"){ + static char len[10]; + int sizelen = snprintf(len, 10, "%x\r\n", size); + //prepend the chunk size and \r\n + conn.SendNow(len, sizelen); + //send the chunk itself + conn.SendNow(data, size); + //append \r\n + conn.SendNow("\r\n", 2); + if ( !size){ + //append \r\n again! + conn.SendNow("\r\n", 2); + } + } +} + /// Unescapes URLencoded std::string data. std::string HTTP::Parser::urlunescape(const std::string & in){ std::string out; diff --git a/lib/http_parser.h b/lib/http_parser.h index bf753190..bb6ff8a5 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -6,6 +6,7 @@ #include #include #include +#include "socket.h" /// Holds all HTTP processing related code. namespace HTTP { @@ -13,6 +14,7 @@ namespace HTTP { class Parser{ public: Parser(); + bool Read(Socket::Connection & conn); bool Read(std::string & strbuf); std::string GetHeader(std::string i); std::string GetVar(std::string i); @@ -24,7 +26,12 @@ namespace HTTP { void SetBody(char * buffer, int len); std::string & BuildRequest(); std::string & BuildResponse(std::string code, std::string message); + void SendRequest(Socket::Connection & conn); + void SendResponse(std::string code, std::string message, Socket::Connection & conn); void Chunkify(std::string & bodypart); + void Chunkify(std::string & bodypart, Socket::Connection & conn); + void Chunkify(const char * data, unsigned int size, Socket::Connection & conn); + void Proxy(Socket::Connection & from, Socket::Connection & to); void Clean(); static std::string urlunescape(const std::string & in); static std::string urlencode(const std::string & in);