diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 0165f464..c60887c7 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -2,13 +2,14 @@ /// Holds all code for the HTTP namespace. #include "http_parser.h" +#include "auth.h" +#include "defines.h" #include "encode.h" #include "timing.h" -#include "defines.h" -#include "auth.h" +#include /// Helper function to check if the given c-string is numeric or not -static bool is_numeric(const char * str){ +static bool is_numeric(const char *str){ while (str[0] != 0){ if (str[0] < 48 || str[0] > 57){return false;} ++str; @@ -16,43 +17,39 @@ static bool is_numeric(const char * str){ return true; } -///Constructor that does the actual parsing -HTTP::URL::URL(const std::string & url){ +/// Constructor that does the actual parsing +HTTP::URL::URL(const std::string &url){ IPv6Addr = false; - //first detect protocol at the start, if any + // first detect protocol at the start, if any size_t proto_sep = url.find("://"); if (proto_sep != std::string::npos){ protocol = url.substr(0, proto_sep); proto_sep += 3; }else{ proto_sep = 0; - if (url.substr(0, 2) == "//"){ - proto_sep = 2; - } + if (url.substr(0, 2) == "//"){proto_sep = 2;} } - //proto_sep now points to the start of the host, guaranteed - //continue by finding the path, if any + // proto_sep now points to the start of the host, guaranteed + // continue by finding the path, if any size_t first_slash = url.find_first_of("/?#", proto_sep); if (first_slash != std::string::npos){ if (url[first_slash] == '/'){ - path = url.substr(first_slash+1); + path = url.substr(first_slash + 1); }else{ path = url.substr(first_slash); } size_t hmark = path.find('#'); if (hmark != std::string::npos){ - frag = Encodings::URL::decode(path.substr(hmark+1)); + frag = Encodings::URL::decode(path.substr(hmark + 1)); path.erase(hmark); } size_t qmark = path.find('?'); if (qmark != std::string::npos){ - args = path.substr(qmark+1); + args = path.substr(qmark + 1); path.erase(qmark); } if (path.size()){ - if (path[0] == '/'){ - path.erase(0, 1); - } + if (path[0] == '/'){path.erase(0, 1);} size_t dots = path.find("/./"); while (dots != std::string::npos){ DONTEVEN_MSG("%s (/./ -> /)", path.c_str()); @@ -65,57 +62,51 @@ HTTP::URL::URL(const std::string & url){ path.erase(dots, 1); dots = path.find("//"); } - if (path[0] == '/'){ - path.erase(0, 1); - } + if (path[0] == '/'){path.erase(0, 1);} dots = path.find("/../"); while (dots != std::string::npos){ - size_t prevslash = path.rfind('/', dots-1); + size_t prevslash = path.rfind('/', dots - 1); if (prevslash == std::string::npos || dots == 0){ - path.erase(0, dots+4); + path.erase(0, dots + 4); }else{ - path.erase(prevslash+1, dots-prevslash+3); + path.erase(prevslash + 1, dots - prevslash + 3); } dots = path.find("/../"); } - if (path.substr(0, 2) == "./"){ - path.erase(0, 2); - } - if (path.substr(0, 3) == "../"){ - path.erase(0, 3); - } + if (path.substr(0, 2) == "./"){path.erase(0, 2);} + if (path.substr(0, 3) == "../"){path.erase(0, 3);} path = Encodings::URL::decode(path); } } - //user, pass, host and port are now definitely between proto_sep and first_slash - std::string uphp = url.substr(proto_sep, first_slash-proto_sep);//user+pass+host+port - //Check if we have a user/pass before the host + // user, pass, host and port are now definitely between proto_sep and first_slash + std::string uphp = url.substr(proto_sep, first_slash - proto_sep); // user+pass+host+port + // Check if we have a user/pass before the host size_t at_sign = uphp.find('@'); if (at_sign != std::string::npos){ std::string creds = uphp.substr(0, at_sign); - uphp.erase(0, at_sign+1); + uphp.erase(0, at_sign + 1); size_t colon = creds.find(':'); if (colon != std::string::npos){ user = Encodings::URL::decode(creds.substr(0, colon)); - pass = Encodings::URL::decode(creds.substr(colon+1)); + pass = Encodings::URL::decode(creds.substr(colon + 1)); }else{ user = Encodings::URL::decode(creds); } } - //we check for [ at the start because we may have an IPv6 address as host + // we check for [ at the start because we may have an IPv6 address as host if (uphp[0] == '['){ - //IPv6 address - find matching brace + // IPv6 address - find matching brace IPv6Addr = true; size_t closing_brace = uphp.find(']'); - host = uphp.substr(1, closing_brace-1); - //continue by finding port, if any + host = uphp.substr(1, closing_brace - 1); + // continue by finding port, if any size_t colon = uphp.find(':', closing_brace); if (colon == std::string::npos){ - //no port. Assume default + // no port. Assume default port = ""; }else{ - //we have a port number, read it - port = uphp.substr(colon+1); + // we have a port number, read it + port = uphp.substr(colon + 1); if (!is_numeric(port.c_str())){ host += ":" + port; port = ""; @@ -125,12 +116,12 @@ HTTP::URL::URL(const std::string & url){ //"normal" host - first find port, if any size_t colon = uphp.rfind(':'); if (colon == std::string::npos){ - //no port. Assume default + // no port. Assume default port = ""; host = uphp; }else{ - //we have a port number, read it - port = uphp.substr(colon+1); + // we have a port number, read it + port = uphp.substr(colon + 1); host = uphp.substr(0, colon); if (!is_numeric(port.c_str())){ IPv6Addr = true; @@ -139,7 +130,7 @@ HTTP::URL::URL(const std::string & url){ } } } - //if the host is numeric, assume it is a port, instead + // if the host is numeric, assume it is a port, instead if (is_numeric(host.c_str())){ port = host; host = ""; @@ -147,13 +138,13 @@ HTTP::URL::URL(const std::string & url){ EXTREME_MSG("URL: %s", getUrl().c_str()); } -///Returns the port in numeric format +/// Returns the port in numeric format uint32_t HTTP::URL::getPort() const{ if (!port.size()){return getDefaultPort();} return atoi(port.c_str()); } -///Returns the default port for the protocol in numeric format +/// Returns the default port for the protocol in numeric format uint32_t HTTP::URL::getDefaultPort() const{ if (protocol == "http"){return 80;} if (protocol == "https"){return 443;} @@ -163,7 +154,7 @@ uint32_t HTTP::URL::getDefaultPort() const{ return 0; } -///Returns the full URL in string format +/// Returns the full URL in string format std::string HTTP::URL::getUrl() const{ std::string ret; if (protocol.size()){ @@ -187,7 +178,27 @@ std::string HTTP::URL::getUrl() const{ return ret; } -///Returns the URL in string format without args and frag +/// Returns the URL in string format without auth and frag +std::string HTTP::URL::getProxyUrl() const{ + std::string ret; + if (protocol.size()){ + ret = protocol + "://"; + }else{ + ret = "//"; + } + if (IPv6Addr){ + ret += "[" + host + "]"; + }else{ + ret += host; + } + if (port.size() && getPort() != getDefaultPort()){ret += ":" + port;} + ret += "/"; + if (path.size()){ret += Encodings::URL::encode(path);} + if (args.size()){ret += "?" + args;} + return ret; +} + +/// Returns the URL in string format without args and frag std::string HTTP::URL::getBareUrl() const{ std::string ret; if (protocol.size()){ @@ -209,55 +220,60 @@ std::string HTTP::URL::getBareUrl() const{ return ret; } -///Returns a URL object for the given link, resolved relative to the current URL object. +/// Returns a URL object for the given link, resolved relative to the current URL object. HTTP::URL HTTP::URL::link(const std::string &l) const{ - //Full link + // Full link if (l.find("://") < l.find('/') && l.find('/' != std::string::npos)){ DONTEVEN_MSG("Full link: %s", l.c_str()); return URL(l); } - //Absolute link + // Absolute link if (l[0] == '/'){ DONTEVEN_MSG("Absolute link: %s", l.c_str()); if (l.size() > 1 && l[1] == '/'){ - //Same-protocol full link - return URL(protocol+":"+l); + // Same-protocol full link + return URL(protocol + ":" + l); }else{ - //Same-domain/port absolute link + // Same-domain/port absolute link URL tmp = *this; tmp.args.clear(); tmp.path = l.substr(1); - //Abuse the fact that we don't check for arguments in getUrl() + // Abuse the fact that we don't check for arguments in getUrl() return URL(tmp.getUrl()); } } - //Relative link + // Relative link std::string tmpUrl = getBareUrl(); size_t slashPos = tmpUrl.rfind('/'); if (slashPos == std::string::npos){ tmpUrl += "/"; }else{ - tmpUrl.erase(slashPos+1); + tmpUrl.erase(slashPos + 1); } DONTEVEN_MSG("Relative link: %s+%s", tmpUrl.c_str(), l.c_str()); - return URL(tmpUrl+l); + return URL(tmpUrl + l); } /// This constructor creates an empty HTTP::Parser, ready for use for either reading or writing. /// All this constructor does is call HTTP::Parser::Clean(). -HTTP::Parser::Parser() { +HTTP::Parser::Parser(){ headerOnly = false; Clean(); + std::stringstream nStr; + nStr << std::hex << std::setw(16) << std::setfill('0') << (uint64_t)(Util::bootMS()); + cnonce = nStr.str(); } -/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage. -void HTTP::Parser::Clean() { +/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing +/// usage. +void HTTP::Parser::Clean(){ CleanPreserveHeaders(); headers.clear(); } -/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing usage. -void HTTP::Parser::CleanPreserveHeaders() { +/// Completely re-initializes the HTTP::Parser, leaving it ready for either reading or writing +/// usage. +void HTTP::Parser::CleanPreserveHeaders(){ seenHeaders = false; seenReq = false; getChunks = false; @@ -273,18 +289,26 @@ void HTTP::Parser::CleanPreserveHeaders() { /// Local-only helper function for use in auth() /// Returns the string contents of the given val from list -static std::string findValIn(const std::string & list, const std::string & val){ - size_t pos = list.find(val+"=\""); +static std::string findValIn(const std::string &list, const std::string &val){ + size_t pos = list.find(val + "="); if (pos == std::string::npos){return "";} - pos += val.size() + 2; + pos += val.size() + 1; if (pos >= list.size()){return "";} - size_t ePos = list.find('"', pos); - if (ePos == std::string::npos){return "";} + size_t ePos; + if (list[pos] == '"'){ + ++pos; + ePos = list.find('"', pos); + if (ePos == std::string::npos){return "";} + }else{ + ePos = list.find(',', pos); + } return list.substr(pos, ePos - pos); } -/// Attempts to send an authentication header with the given name and password. Uses authReq as WWW-Authenticate header. -void HTTP::Parser::auth(const std::string & user, const std::string & pass, const std::string & authReq){ +/// Attempts to send an authentication header with the given name and password. Uses authReq as +/// WWW-Authenticate header. +void HTTP::Parser::auth(const std::string &user, const std::string &pass, + const std::string &authReq, const std::string &headerName){ size_t space = authReq.find(' '); if (space == std::string::npos || !user.size() || !pass.size()){ FAIL_MSG("No authentication possible"); @@ -292,15 +316,48 @@ void HTTP::Parser::auth(const std::string & user, const std::string & pass, cons } std::string meth = authReq.substr(0, space); if (meth == "Basic"){ - SetHeader("Authorization", "Basic "+Encodings::Base64::encode(user+":"+pass)); + SetHeader(headerName, "Basic " + Encodings::Base64::encode(user + ":" + pass)); return; } if (meth == "Digest"){ - std::string realm=findValIn(authReq, "realm"), nonce=findValIn(authReq, "nonce"); - std::string A1 = Secure::md5(user+":"+realm+":"+pass); - std::string A2 = Secure::md5(method+":"+url); - std::string response = Secure::md5(A1+":"+nonce+":"+A2); - SetHeader("Authorization", "Digest username=\""+user+"\", realm=\""+realm+"\", nonce=\""+nonce+"\", uri=\""+url+"\", response=\""+response+"\""); + std::string realm = findValIn(authReq, "realm"), nonce = findValIn(authReq, "nonce"), + opaque = findValIn(authReq, "opaque"), qop = findValIn(authReq, "qop"), + algo = findValIn(authReq, "algorithm"); + std::string A1 = Secure::md5(user + ":" + realm + ":" + pass); + if (algo.size() && algo != "MD5"){ + FAIL_MSG("Authorization algorithm %s not implemented", algo.c_str()); + return; + } + std::string urlPart; + if (url.find("://") != std::string::npos){ + HTTP::URL tmpUrl(url); + urlPart = "/" + tmpUrl.path; + }else{ + urlPart = url; + } + algo = "MD5"; + std::string A2 = Secure::md5(method + ":" + urlPart); + std::string response; + static uint32_t nc = 0; + std::string ncStr; + if (qop.size()){ + ++nc; + std::stringstream nHex; + nHex << std::hex << std::setw(8) << std::setfill('0') << nc; + ncStr = nHex.str(); + response = Secure::md5(A1 + ":" + nonce + ":" + ncStr + ":" + cnonce + ":auth:" + A2); + }else{ + response = Secure::md5(A1 + ":" + nonce + ":" + A2); + } + std::stringstream rep; + // username | realm | nonce | digest-uri | response | [ algorithm ] | [cnonce] | [opaque] | + // [message-qop] | [nonce-count] + rep << "Digest username=\"" << user << "\", realm=\"" << realm << "\", nonce=\"" << nonce + << "\", uri=\"" << urlPart << "\", response=\"" << response << "\", algorithm=" + algo; + if (qop.size()){rep << ", cnonce=\"" << cnonce << "\"";} + if (opaque.size()){rep << ", opaque=\"" << opaque << "\"";} + if (qop.size()){rep << ", qop=auth, nc=" << ncStr;} + SetHeader(headerName, rep.str()); return; } FAIL_MSG("No authentication possible, unimplemented method '%s'", meth.c_str()); @@ -315,26 +372,23 @@ void HTTP::Parser::setCORSHeaders(){ SetHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD"); SetHeader("Access-Control-Allow-Headers", "*"); SetHeader("Access-Control-Request-Method", "GET"); - SetHeader("Access-Control-Request-Headers", "*"); + SetHeader("Access-Control-Request-Headers", "*"); SetHeader("Cache-Control", "no-cache, no-store, must-revalidate"); SetHeader("Pragma", "no-cache"); SetHeader("Expires", "0"); } - /// Returns a string containing a valid HTTP 1.0 or 1.1 request, ready for sending. /// The request is build from internal variables set before this call is made. /// To be precise, method, url, protocol, headers and body are used. /// \return A string containing a valid HTTP 1.0 or 1.1 request, ready for sending. -std::string & HTTP::Parser::BuildRequest() { +std::string &HTTP::Parser::BuildRequest(){ /// \todo Include GET/POST variable parsing? std::map::iterator it; - if (protocol.size() < 5 || protocol[4] != '/') { - protocol = "HTTP/1.0"; - } + if (protocol.size() < 5 || protocol[4] != '/'){protocol = "HTTP/1.0";} builder = method + " " + url + " " + protocol + "\r\n"; - for (it = headers.begin(); it != headers.end(); it++) { - if ((*it).first != "" && (*it).second != "") { + for (it = headers.begin(); it != headers.end(); it++){ + if ((*it).first != "" && (*it).second != ""){ builder += (*it).first + ": " + (*it).second + "\r\n"; } } @@ -345,22 +399,25 @@ std::string & HTTP::Parser::BuildRequest() { /// 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) { +void HTTP::Parser::SendRequest(Socket::Connection &conn, const std::string &reqbody){ /// \todo Include GET/POST variable parsing? std::map::iterator it; - if (protocol.size() < 5 || protocol[4] != '/') { - protocol = "HTTP/1.0"; - } + if (protocol.size() < 5 || protocol[4] != '/'){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 != "") { + if (reqbody.size()){SetHeader("Content-Length", reqbody.length());} + 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); + if (reqbody.size()){ + conn.SendNow(reqbody); + }else{ + conn.SendNow(body); + } } /// Returns a string containing a valid HTTP 1.0 or 1.1 response, ready for sending. @@ -369,16 +426,14 @@ void HTTP::Parser::SendRequest(Socket::Connection & conn) { /// \param code The HTTP response code. Usually you want 200. /// \param message The HTTP response message. Usually you want "OK". /// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending. -std::string & HTTP::Parser::BuildResponse(std::string code, std::string message) { +std::string &HTTP::Parser::BuildResponse(std::string code, std::string message){ /// \todo Include GET/POST variable parsing? std::map::iterator it; - if (protocol.size() < 5 || protocol[4] != '/') { - protocol = "HTTP/1.0"; - } + if (protocol.size() < 5 || protocol[4] != '/'){protocol = "HTTP/1.0";} builder = protocol + " " + code + " " + message + "\r\n"; - for (it = headers.begin(); it != headers.end(); it++) { - if ((*it).first != "" && (*it).second != "") { - if ((*it).first != "Content-Length" || (*it).second != "0") { + 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"; } } @@ -393,28 +448,25 @@ std::string & HTTP::Parser::BuildResponse(std::string code, std::string message) /// To be precise, protocol, headers and body are used. /// \return A string containing a valid HTTP 1.0 or 1.1 response, ready for sending. /// This function calls this->BuildResponse(this->method,this->url) -std::string & HTTP::Parser::BuildResponse() { - return BuildResponse(method,url); +std::string &HTTP::Parser::BuildResponse(){ + return BuildResponse(method, url); } /// 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. -/// This call will attempt to buffer as little as possible and block until the whole request is sent. -/// \param code The HTTP response code. Usually you want 200. -/// \param message The HTTP response message. Usually you want "OK". -/// \param conn The Socket::Connection to send the response over. -void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::Connection & conn) { +/// This call will attempt to buffer as little as possible and block until the whole request is +/// sent. \param code The HTTP response code. Usually you want 200. \param message The HTTP response +/// message. Usually you want "OK". \param conn The Socket::Connection to send the response over. +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[4] != '/') { - protocol = "HTTP/1.0"; - } + if (protocol.size() < 5 || protocol[4] != '/'){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") { + 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); } @@ -426,90 +478,89 @@ void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::C /// Creates and sends a valid HTTP 1.0 or 1.1 response, based on the given request. /// The headers must be set before this call is made. -/// This call sets up chunked transfer encoding if the request was protocol HTTP/1.1, otherwise uses a zero-content-length HTTP/1.0 response. -/// \param code The HTTP response code. Usually you want 200. -/// \param message The HTTP response message. Usually you want "OK". -/// \param request The HTTP request to respond to. -/// \param conn The connection to send over. -void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser & request, Socket::Connection & conn, bool bufferAllChunks) { +/// This call sets up chunked transfer encoding if the request was protocol HTTP/1.1, otherwise uses +/// a zero-content-length HTTP/1.0 response. \param code The HTTP response code. Usually you want +/// 200. \param message The HTTP response message. Usually you want "OK". \param request The HTTP +/// request to respond to. \param conn The connection to send over. +void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser &request, + Socket::Connection &conn, bool bufferAllChunks){ std::string prot = request.protocol; - sendingChunks = (!bufferAllChunks && protocol == "HTTP/1.1" && request.GetHeader("Connection")!="close"); + sendingChunks = + (!bufferAllChunks && protocol == "HTTP/1.1" && request.GetHeader("Connection") != "close"); CleanPreserveHeaders(); protocol = prot; if (sendingChunks){ SetHeader("Transfer-Encoding", "chunked"); - } else { + }else{ SetHeader("Connection", "close"); } bufferChunks = bufferAllChunks; - if (!bufferAllChunks){ - SendResponse(code, message, conn); - } + if (!bufferAllChunks){SendResponse(code, message, conn);} } /// Creates and sends a valid HTTP 1.0 or 1.1 response, based on the given request. /// The headers must be set before this call is made. -/// This call sets up chunked transfer encoding if the request was protocol HTTP/1.1, otherwise uses a zero-content-length HTTP/1.0 response. -/// This call simply calls StartResponse("200", "OK", request, conn) -/// \param request The HTTP request to respond to. -/// \param conn The connection to send over. -void HTTP::Parser::StartResponse(HTTP::Parser & request, Socket::Connection & conn, bool bufferAllChunks) { +/// This call sets up chunked transfer encoding if the request was protocol HTTP/1.1, otherwise uses +/// a zero-content-length HTTP/1.0 response. This call simply calls StartResponse("200", "OK", +/// request, conn) \param request The HTTP request to respond to. \param conn The connection to send +/// over. +void HTTP::Parser::StartResponse(HTTP::Parser &request, Socket::Connection &conn, + bool bufferAllChunks){ StartResponse("200", "OK", request, conn, bufferAllChunks); } -/// After receiving a header with this object, and after a call with SendResponse/SendRequest with this object, this function call will: +/// After receiving a header with this object, and after a call with SendResponse/SendRequest with +/// this object, this function call will: /// - 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) { - if (getChunks) { +void HTTP::Parser::Proxy(Socket::Connection &from, Socket::Connection &to){ + if (getChunks){ unsigned int proxyingChunk = 0; - while (to.connected() && from.connected()) { - if ((from.Received().size() && (from.Received().size() > 1 || *(from.Received().get().rbegin()) == '\n')) || from.spool()) { - if (proxyingChunk) { - while (proxyingChunk && from.Received().size()) { + while (to.connected() && from.connected()){ + if ((from.Received().size() && + (from.Received().size() > 1 || *(from.Received().get().rbegin()) == '\n')) || + from.spool()){ + if (proxyingChunk){ + while (proxyingChunk && from.Received().size()){ unsigned int toappend = from.Received().get().size(); - if (toappend > proxyingChunk) { + if (toappend > proxyingChunk){ toappend = proxyingChunk; to.SendNow(from.Received().get().c_str(), toappend); from.Received().get().erase(0, toappend); - } else { + }else{ to.SendNow(from.Received().get()); from.Received().get().clear(); } proxyingChunk -= toappend; } - } else { - //Make sure the received data ends in a newline (\n). - if (*(from.Received().get().rbegin()) != '\n') { - if (from.Received().size() > 1) { - //make a copy of the first part + }else{ + // Make sure the received data ends in a newline (\n). + if (*(from.Received().get().rbegin()) != '\n'){ + if (from.Received().size() > 1){ + // make a copy of the first part std::string tmp = from.Received().get(); - //clear the first part, wiping it from the partlist + // clear the first part, wiping it from the partlist from.Received().get().clear(); from.Received().size(); - //take the now first (was second) part, insert the stored part in front of it + // take the now first (was second) part, insert the stored part in front of it from.Received().get().insert(0, tmp); - } else { + }else{ Util::sleep(100); } - if (*(from.Received().get().rbegin()) != '\n') { - continue; - } + if (*(from.Received().get().rbegin()) != '\n'){continue;} } - //forward the size and any empty lines + // 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')); - } + while (tmpA.find('\r') != std::string::npos){tmpA.erase(tmpA.find('\r'));} unsigned int chunkLen = 0; - if (!tmpA.empty()) { - for (unsigned int i = 0; i < tmpA.size(); ++i) { + if (!tmpA.empty()){ + for (unsigned int i = 0; i < tmpA.size(); ++i){ chunkLen = (chunkLen << 4) | Encodings::Hex::ord(tmpA[i]); } - if (chunkLen == 0) { + if (chunkLen == 0){ getChunks = false; to.SendNow("\r\n", 2); return; @@ -518,24 +569,24 @@ void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) { } from.Received().get().clear(); } - } else { + }else{ Util::sleep(100); } } - } else { + }else{ unsigned int bodyLen = length; - while (bodyLen > 0 && to.connected() && from.connected()) { - if (from.Received().size() || from.spool()) { - if (from.Received().get().size() <= bodyLen) { + 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 { + }else{ to.SendNow(from.Received().get().c_str(), bodyLen); from.Received().get().erase(0, bodyLen); bodyLen = 0; } - } else { + }else{ Util::sleep(100); } } @@ -545,43 +596,42 @@ void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) { /// 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. -void HTTP::Parser::Trim(std::string & s) { +void HTTP::Parser::Trim(std::string &s){ size_t startpos = s.find_first_not_of(" \t"); size_t endpos = s.find_last_not_of(" \t"); - if ((std::string::npos == startpos) || (std::string::npos == endpos)) { + if ((std::string::npos == startpos) || (std::string::npos == endpos)){ s = ""; - } else { + }else{ s = s.substr(startpos, endpos - startpos + 1); } } -/// Function that sets the body of a response or request, along with the correct Content-Length header. -/// \param s The string to set the body to. -void HTTP::Parser::SetBody(std::string s) { +/// Function that sets the body of a response or request, along with the correct Content-Length +/// header. \param s The string to set the body to. +void HTTP::Parser::SetBody(std::string s){ body = s; SetHeader("Content-Length", s.length()); } -/// Function that sets the body of a response or request, along with the correct Content-Length header. -/// \param buffer The buffer data to set the body to. -/// \param len Length of the buffer data. -void HTTP::Parser::SetBody(const char * buffer, int len) { +/// Function that sets the body of a response or request, along with the correct Content-Length +/// header. \param buffer The buffer data to set the body to. \param len Length of the buffer data. +void HTTP::Parser::SetBody(const char *buffer, int len){ body = ""; body.append(buffer, len); SetHeader("Content-Length", len); } /// Returns header i, if set. -std::string HTTP::Parser::getUrl() { - if (url.find('?') != std::string::npos) { +std::string HTTP::Parser::getUrl(){ + if (url.find('?') != std::string::npos){ return url.substr(0, url.find('?')); - } else { + }else{ return url; } } /// Returns header i, if set. -const std::string & HTTP::Parser::GetHeader(const std::string & i) const { +const std::string &HTTP::Parser::GetHeader(const std::string &i) const{ if (headers.count(i)){ return headers.at(i); }else{ @@ -591,12 +641,12 @@ const std::string & HTTP::Parser::GetHeader(const std::string & i) const { } /// Returns header i, if set. -bool HTTP::Parser::hasHeader(const std::string & i) const { +bool HTTP::Parser::hasHeader(const std::string &i) const{ return headers.count(i); } /// Returns POST variable i, if set. -const std::string & HTTP::Parser::GetVar(const std::string & i) const { +const std::string &HTTP::Parser::GetVar(const std::string &i) const{ if (vars.count(i)){ return vars.at(i); }else{ @@ -621,151 +671,141 @@ std::string HTTP::Parser::allVars(){ } /// Sets header i to string value v. -void HTTP::Parser::SetHeader(std::string i, std::string v) { +void HTTP::Parser::SetHeader(std::string i, std::string v){ Trim(i); Trim(v); headers[i] = v; } -void HTTP::Parser::clearHeader(const std::string & i){ +void HTTP::Parser::clearHeader(const std::string &i){ headers.erase(i); } /// Sets header i to integer value v. -void HTTP::Parser::SetHeader(std::string i, long long v) { +void HTTP::Parser::SetHeader(std::string i, long long v){ Trim(i); - char val[23]; //ints are never bigger than 22 chars as decimal + char val[23]; // ints are never bigger than 22 chars as decimal sprintf(val, "%lld", v); headers[i] = val; } /// Sets POST variable i to string value v. -void HTTP::Parser::SetVar(std::string i, std::string v) { +void HTTP::Parser::SetVar(std::string i, std::string v){ Trim(i); Trim(v); - //only set if there is actually a key - if (!i.empty()) { - vars[i] = v; - } + // only set if there is actually a key + if (!i.empty()){vars[i] = 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). - while ((!seenHeaders || (getChunks && !doingChunk)) && conn.Received().get().size() && *(conn.Received().get().rbegin()) != '\n') { - if (conn.Received().size() > 1) { - //make a copy of the first part +/// 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). + while ((!seenHeaders || (getChunks && !doingChunk)) && conn.Received().get().size() && + *(conn.Received().get().rbegin()) != '\n'){ + if (conn.Received().size() > 1){ + // make a copy of the first part std::string tmp = conn.Received().get(); - //clear the first part, wiping it from the partlist + // clear the first part, wiping it from the partlist conn.Received().get().clear(); conn.Received().size(); - //take the now first (was second) part, insert the stored part in front of it + // take the now first (was second) part, insert the stored part in front of it conn.Received().get().insert(0, tmp); - } else { + }else{ return false; } } - //if a parse succeeds, simply return true - if (parse(conn.Received().get())) { - return true; - } - //otherwise, if we have parts left, call ourselves recursively - if (conn.Received().size()) { - return Read(conn); - } + // if a parse succeeds, simply return true + if (parse(conn.Received().get())){return true;} + // otherwise, if we have parts left, call ourselves recursively + if (conn.Received().size()){return Read(conn);} return false; -} //HTTPReader::Read +}// 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 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) { +/// 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 +}// HTTPReader::Read /// 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. /// \param HTTPbuffer The data buffer to read from. /// \return True on success, false otherwise. -bool HTTP::Parser::parse(std::string & HTTPbuffer) { +bool HTTP::Parser::parse(std::string &HTTPbuffer){ size_t f; std::string tmpA, tmpB, tmpC; - /// \todo Make this not resize HTTPbuffer in parts, but read all at once and then remove the entire request, like doxygen claims it does? - while (!HTTPbuffer.empty()) { - if (!seenHeaders) { + /// \todo Make this not resize HTTPbuffer in parts, but read all at once and then remove the + /// entire request, like doxygen claims it does? + while (!HTTPbuffer.empty()){ + if (!seenHeaders){ f = HTTPbuffer.find('\n'); if (f == std::string::npos) return false; tmpA = HTTPbuffer.substr(0, f); - if (f + 1 == HTTPbuffer.size()) { + if (f + 1 == HTTPbuffer.size()){ HTTPbuffer.clear(); - } else { + }else{ HTTPbuffer.erase(0, f + 1); } - while (tmpA.find('\r') != std::string::npos) { - tmpA.erase(tmpA.find('\r')); - } - if (!seenReq) { + while (tmpA.find('\r') != std::string::npos){tmpA.erase(tmpA.find('\r'));} + if (!seenReq){ seenReq = true; f = tmpA.find(' '); - if (f != std::string::npos) { - if (tmpA.substr(0, 4) == "HTTP") { + if (f != std::string::npos){ + if (tmpA.substr(0, 4) == "HTTP"){ protocol = tmpA.substr(0, f); tmpA.erase(0, f + 1); f = tmpA.find(' '); - if (f != std::string::npos) { + 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), vars); //parse GET variables + if (url.find('?') != std::string::npos){ + parseVars(url.substr(url.find('?') + 1), vars); // parse GET variables url.erase(url.find('?')); } url = Encodings::URL::decode(url); - } else { + }else{ seenReq = false; } - } else { + }else{ method = tmpA.substr(0, f); tmpA.erase(0, f + 1); f = tmpA.find(' '); - if (f != std::string::npos) { + 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), vars); //parse GET variables + if (url.find('?') != std::string::npos){ + parseVars(url.substr(url.find('?') + 1), vars); // parse GET variables url.erase(url.find('?')); } url = Encodings::URL::decode(url); - } else { + }else{ seenReq = false; } } - } else { + }else{ seenReq = false; } - } else { - if (tmpA.size() == 0) { + }else{ + if (tmpA.size() == 0){ seenHeaders = true; body.clear(); - if (GetHeader("Content-Length") != "") { + if (GetHeader("Content-Length") != ""){ length = atoi(GetHeader("Content-Length").c_str()); - if (body.capacity() < length) { - body.reserve(length); - } + if (body.capacity() < length){body.reserve(length);} } - if (GetHeader("Transfer-Encoding") == "chunked") { + if (GetHeader("Transfer-Encoding") == "chunked"){ getChunks = true; doingChunk = 0; } - } else { + }else{ f = tmpA.find(':'); if (f == std::string::npos) continue; tmpB = tmpA.substr(0, f); @@ -774,87 +814,77 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer) { } } } - if (seenHeaders) { - if (length > 0) { - if (headerOnly) { - return true; - } + if (seenHeaders){ + if (length > 0){ + if (headerOnly){return true;} unsigned int toappend = length - body.length(); - if (toappend > 0) { + if (toappend > 0){ body.append(HTTPbuffer, 0, toappend); HTTPbuffer.erase(0, toappend); } - if (length == body.length()) { - parseVars(body, vars); //parse POST variables + if (length == body.length()){ + parseVars(body, vars); // parse POST variables return true; - } else { + }else{ return false; } - } else { - if (getChunks) { - if (headerOnly) { - return true; - } - if (doingChunk) { + }else{ + if (getChunks){ + if (headerOnly){return true;} + if (doingChunk){ unsigned int toappend = HTTPbuffer.size(); - if (toappend > doingChunk) { - toappend = doingChunk; - } + if (toappend > doingChunk){toappend = doingChunk;} body.append(HTTPbuffer, 0, toappend); HTTPbuffer.erase(0, toappend); doingChunk -= toappend; - } else { + }else{ f = HTTPbuffer.find('\n'); if (f == std::string::npos) return false; tmpA = HTTPbuffer.substr(0, f); - while (tmpA.find('\r') != std::string::npos) { - tmpA.erase(tmpA.find('\r')); - } + while (tmpA.find('\r') != std::string::npos){tmpA.erase(tmpA.find('\r'));} unsigned int chunkLen = 0; - if (!tmpA.empty()) { - for (unsigned int i = 0; i < tmpA.size(); ++i) { + if (!tmpA.empty()){ + for (unsigned int i = 0; i < tmpA.size(); ++i){ chunkLen = (chunkLen << 4) | Encodings::Hex::ord(tmpA[i]); } - if (chunkLen == 0) { + if (chunkLen == 0){ getChunks = false; return true; } doingChunk = chunkLen; } - if (f + 1 == HTTPbuffer.size()) { + if (f + 1 == HTTPbuffer.size()){ HTTPbuffer.clear(); - } else { + }else{ HTTPbuffer.erase(0, f + 1); } } return false; - } else { + }else{ return true; } } } } - return false; //empty input -} //HTTPReader::parse + return false; // empty input +}// HTTPReader::parse -///HTTP variable parser to std::map structure. -///Reads variables from data, decodes and stores them to storage. -void HTTP::parseVars(const std::string & data, std::map & storage) { +/// HTTP variable parser to std::map structure. +/// Reads variables from data, decodes and stores them to storage. +void HTTP::parseVars(const std::string &data, std::map &storage){ std::string varname; std::string varval; // position where a part starts (e.g. after &) size_t pos = 0; - while (pos < data.length()) { + while (pos < data.length()){ size_t nextpos = data.find('&', pos); - if (nextpos == std::string::npos) { - nextpos = data.length(); - } + if (nextpos == std::string::npos){nextpos = data.length();} size_t eq_pos = data.find('=', pos); - if (eq_pos < nextpos) { + if (eq_pos < nextpos){ // there is a key and value varname = data.substr(pos, eq_pos - pos); varval = data.substr(eq_pos + 1, nextpos - eq_pos - 1); - } else { + }else{ // no value, only a key varname = data.substr(pos, nextpos - pos); varval.clear(); @@ -862,7 +892,7 @@ void HTTP::parseVars(const std::string & data, std::map>= 4; } - conn.SendNow(len+offset, 10-offset); - //send the chunk itself + conn.SendNow(len + offset, 10 - offset); + // send the chunk itself conn.SendNow(data, size); - //append \r\n + // append \r\n conn.SendNow("\r\n", 2); - } else { - //just send the chunk itself + }else{ + // just send the chunk itself conn.SendNow(data, size); - //close the connection if this was the end of the file - if (!size) { + // close the connection if this was the end of the file + if (!size){ conn.close(); Clean(); } diff --git a/lib/http_parser.h b/lib/http_parser.h index 223be8a9..53dccbbe 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -2,92 +2,96 @@ /// Holds all headers for the HTTP namespace. #pragma once -#include -#include -#include -#include #include "socket.h" +#include +#include +#include +#include /// Holds all HTTP processing related code. -namespace HTTP { - - ///HTTP variable parser to std::map structure. - ///Reads variables from data, decodes and stores them to storage. - void parseVars(const std::string & data, std::map & storage); +namespace HTTP{ + /// HTTP variable parser to std::map structure. + /// Reads variables from data, decodes and stores them to storage. + void parseVars(const std::string &data, std::map &storage); /// Simple class for reading and writing HTTP 1.0 and 1.1. - class Parser { - public: - Parser(); - bool Read(Socket::Connection & conn); - bool Read(std::string & strbuf); - const std::string & GetHeader(const std::string & i) const; - bool hasHeader(const std::string & i) const; - void clearHeader(const std::string & i); - const std::string & GetVar(const std::string & i) const; - std::string getUrl(); - std::string allVars(); - void SetHeader(std::string i, std::string v); - void SetHeader(std::string i, long long v); - void setCORSHeaders(); - void SetVar(std::string i, std::string v); - void SetBody(std::string s); - void SetBody(const char * buffer, int len); - std::string & BuildRequest(); - std::string & BuildResponse(); - 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 StartResponse(std::string code, std::string message, Parser & request, Socket::Connection & conn, bool bufferAllChunks = false); - void StartResponse(Parser & request, Socket::Connection & conn, bool bufferAllChunks = false); - void Chunkify(const 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(); - void CleanPreserveHeaders(); - void auth(const std::string & user, const std::string & pass, const std::string & authReq); - std::string body; - std::string method; - std::string url; - std::string protocol; - unsigned int length; - bool headerOnly; ///< If true, do not parse body if the length is a known size. - bool bufferChunks; - //this bool was private - bool sendingChunks; + class Parser{ + public: + Parser(); + bool Read(Socket::Connection &conn); + bool Read(std::string &strbuf); + const std::string &GetHeader(const std::string &i) const; + bool hasHeader(const std::string &i) const; + void clearHeader(const std::string &i); + const std::string &GetVar(const std::string &i) const; + std::string getUrl(); + std::string allVars(); + void SetHeader(std::string i, std::string v); + void SetHeader(std::string i, long long v); + void setCORSHeaders(); + void SetVar(std::string i, std::string v); + void SetBody(std::string s); + void SetBody(const char *buffer, int len); + std::string &BuildRequest(); + std::string &BuildResponse(); + std::string &BuildResponse(std::string code, std::string message); + void SendRequest(Socket::Connection &conn, const std::string &reqbody = ""); + void SendResponse(std::string code, std::string message, Socket::Connection &conn); + void StartResponse(std::string code, std::string message, Parser &request, + Socket::Connection &conn, bool bufferAllChunks = false); + void StartResponse(Parser &request, Socket::Connection &conn, bool bufferAllChunks = false); + void Chunkify(const 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(); + void CleanPreserveHeaders(); + void auth(const std::string &user, const std::string &pass, const std::string &authReq, + const std::string &headerName = "Authorization"); + std::string body; + std::string method; + std::string url; + std::string protocol; + unsigned int length; + bool headerOnly; ///< If true, do not parse body if the length is a known size. + bool bufferChunks; + // this bool was private + bool sendingChunks; - private: - bool seenHeaders; - bool seenReq; - bool getChunks; - unsigned int doingChunk; - bool parse(std::string & HTTPbuffer); - std::string builder; - std::string read_buffer; - std::map headers; - std::map vars; - void Trim(std::string & s); - }; - - ///URL parsing class. Parses full URL into its subcomponents - class URL { - public: - URL(const std::string & url = ""); - uint32_t getPort() const; - uint32_t getDefaultPort() const; - std::string getUrl() const; - std::string getBareUrl() const; - std::string host;///< Hostname or IP address of URL - std::string protocol;/// headers; + std::map vars; + void Trim(std::string &s); }; -}//HTTP namespace + /// URL parsing class. Parses full URL into its subcomponents + class URL{ + public: + URL(const std::string &url = ""); + uint32_t getPort() const; + uint32_t getDefaultPort() const; + std::string getUrl() const; + std::string getBareUrl() const; + std::string getProxyUrl() const; + std::string host; ///< Hostname or IP address of URL + std::string protocol; ///< Protocol of URL + std::string port; ///< Port of URL + std::string path; ///< Path after the first slash (not inclusive) but before any question mark + std::string args; ///< Everything after the question mark in the path, if it was present + std::string frag; ///< Everything after the # in the path, if it was present + std::string user; ///< Username, if it was present + std::string pass; ///< Password, if it was present + URL link(const std::string &l) const; + bool IPv6Addr; + }; + +}// namespace HTTP +