diff --git a/libi2pd/HTTP.cpp b/libi2pd/HTTP.cpp index c1a46e73..aaba3abc 100644 --- a/libi2pd/HTTP.cpp +++ b/libi2pd/HTTP.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2017, The PurpleI2P Project +* Copyright (c) 2013-2019, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -15,282 +15,283 @@ namespace i2p { namespace http { - const std::vector HTTP_METHODS = { - "GET", "HEAD", "POST", "PUT", "PATCH", - "DELETE", "OPTIONS", "CONNECT" - }; - const std::vector HTTP_VERSIONS = { - "HTTP/1.0", "HTTP/1.1" - }; - const std::vector weekdays = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" - }; - const std::vector months = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" - }; - - inline bool is_http_version(const std::string & str) { - return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); - } - - inline bool is_http_method(const std::string & str) { - return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); - } - - void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { - std::size_t count = 0; - std::stringstream ss(line); - std::string token; - while (1) { - count++; - if (limit > 0 && count >= limit) - delim = '\n'; /* reset delimiter */ - if (!std::getline(ss, token, delim)) - break; - tokens.push_back(token); - } - } - - static std::pair parse_header_line(const std::string& line) - { - std::size_t pos = 0; - std::size_t len = 1; /*: */ - std::size_t max = line.length(); - if ((pos = line.find(':', pos)) == std::string::npos) - return std::make_pair("", ""); // no ':' found - if (pos + 1 < max) // ':' at the end of header is valid - { - while ((pos + len) < max && isspace(line.at(pos + len))) - len++; - if (len == 1) return std::make_pair("", ""); // no following space, but something else + const std::vector HTTP_METHODS = { + "GET", "HEAD", "POST", "PUT", "PATCH", + "DELETE", "OPTIONS", "CONNECT" + }; + const std::vector HTTP_VERSIONS = { + "HTTP/1.0", "HTTP/1.1" + }; + const std::vector weekdays = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + const std::vector months = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + inline bool is_http_version(const std::string & str) { + return std::find(HTTP_VERSIONS.begin(), HTTP_VERSIONS.end(), str) != std::end(HTTP_VERSIONS); + } + + inline bool is_http_method(const std::string & str) { + return std::find(HTTP_METHODS.begin(), HTTP_METHODS.end(), str) != std::end(HTTP_METHODS); + } + + void strsplit(const std::string & line, std::vector &tokens, char delim, std::size_t limit = 0) { + std::size_t count = 0; + std::stringstream ss(line); + std::string token; + while (1) { + count++; + if (limit > 0 && count >= limit) + delim = '\n'; /* reset delimiter */ + if (!std::getline(ss, token, delim)) + break; + tokens.push_back(token); + } + } + + static std::pair parse_header_line(const std::string& line) + { + std::size_t pos = 0; + std::size_t len = 1; /*: */ + std::size_t max = line.length(); + if ((pos = line.find(':', pos)) == std::string::npos) + return std::make_pair("", ""); // no ':' found + if (pos + 1 < max) // ':' at the end of header is valid + { + while ((pos + len) < max && isspace(line.at(pos + len))) + len++; + if (len == 1) + return std::make_pair("", ""); // no following space, but something else + } + return std::make_pair(line.substr(0, pos), line.substr(pos + len)); + } + + void gen_rfc7231_date(std::string & out) { + std::time_t now = std::time(nullptr); + char buf[128]; + std::tm *tm = std::gmtime(&now); + snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", + weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], + tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec + ); + out = buf; + } + + bool URL::parse(const char *str, std::size_t len) { + std::string url(str, len ? len : strlen(str)); + return parse(url); + } + + bool URL::parse(const std::string& url) { + std::size_t pos_p = 0; /* < current parse position */ + std::size_t pos_c = 0; /* < work position */ + if(url.at(0) != '/' || pos_p > 0) { + std::size_t pos_s = 0; + /* schema */ + pos_c = url.find("://"); + if (pos_c != std::string::npos) { + schema = url.substr(0, pos_c); + pos_p = pos_c + 3; + } + /* user[:pass] */ + pos_s = url.find('/', pos_p); /* find first slash */ + pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */ + if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) { + std::size_t delim = url.find(':', pos_p); + if (delim && delim != std::string::npos && delim < pos_c) { + user = url.substr(pos_p, delim - pos_p); + delim += 1; + pass = url.substr(delim, pos_c - delim); + } else if(delim) { + user = url.substr(pos_p, pos_c - pos_p); + } + pos_p = pos_c + 1; + } + /* hostname[:port][/path] */ + pos_c = url.find_first_of(":/", pos_p); + if (pos_c == std::string::npos) { + /* only hostname, without post and path */ + host = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == ':') { + host = url.substr(pos_p, pos_c - pos_p); + /* port[/path] */ + pos_p = pos_c + 1; + pos_c = url.find('/', pos_p); + std::string port_str = (pos_c == std::string::npos) + ? url.substr(pos_p, std::string::npos) + : url.substr(pos_p, pos_c - pos_p); + /* stoi throws exception on failure, we don't need it */ + for (char c : port_str) { + if (c < '0' || c > '9') + return false; + port *= 10; + port += c - '0'; + } + if (pos_c == std::string::npos) + return true; /* no path part */ + pos_p = pos_c; + } else { + /* start of path part found */ + host = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c; + } + } + + /* pos_p now at start of path part */ + pos_c = url.find_first_of("?#", pos_p); + if (pos_c == std::string::npos) { + /* only path, without fragment and query */ + path = url.substr(pos_p, std::string::npos); + return true; + } else if (url.at(pos_c) == '?') { + /* found query part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + pos_c = url.find('#', pos_p); + if (pos_c == std::string::npos) { + /* no fragment */ + query = url.substr(pos_p, std::string::npos); + return true; + } else { + query = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } + } else { + /* found fragment part */ + path = url.substr(pos_p, pos_c - pos_p); + pos_p = pos_c + 1; + } + + /* pos_p now at start of fragment part */ + frag = url.substr(pos_p, std::string::npos); + return true; + } + + bool URL::parse_query(std::map & params) { + std::vector tokens; + strsplit(query, tokens, '&'); + + params.clear(); + for (const auto& it : tokens) { + std::size_t eq = it.find ('='); + if (eq != std::string::npos) { + auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); + params.insert(e); + } else { + auto e = std::pair(it, ""); + params.insert(e); + } + } + return true; + } + + std::string URL::to_string() { + std::string out = ""; + if (schema != "") { + out = schema + "://"; + if (user != "" && pass != "") { + out += user + ":" + pass + "@"; + } else if (user != "") { + out += user + "@"; + } + if (port) { + out += host + ":" + std::to_string(port); + } else { + out += host; + } + } + out += path; + if (query != "") + out += "?" + query; + if (frag != "") + out += "#" + frag; + return out; } - return std::make_pair(line.substr(0, pos), line.substr(pos + len)); - } - - void gen_rfc7231_date(std::string & out) { - std::time_t now = std::time(nullptr); - char buf[128]; - std::tm *tm = std::gmtime(&now); - snprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", - weekdays[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], - tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec - ); - out = buf; - } - - bool URL::parse(const char *str, std::size_t len) { - std::string url(str, len ? len : strlen(str)); - return parse(url); - } - - bool URL::parse(const std::string& url) { - std::size_t pos_p = 0; /* < current parse position */ - std::size_t pos_c = 0; /* < work position */ - if(url.at(0) != '/' || pos_p > 0) { - std::size_t pos_s = 0; - /* schema */ - pos_c = url.find("://"); - if (pos_c != std::string::npos) { - schema = url.substr(0, pos_c); - pos_p = pos_c + 3; - } - /* user[:pass] */ - pos_s = url.find('/', pos_p); /* find first slash */ - pos_c = url.find('@', pos_p); /* find end of 'user' or 'user:pass' part */ - if (pos_c != std::string::npos && (pos_s == std::string::npos || pos_s > pos_c)) { - std::size_t delim = url.find(':', pos_p); - if (delim && delim != std::string::npos && delim < pos_c) { - user = url.substr(pos_p, delim - pos_p); - delim += 1; - pass = url.substr(delim, pos_c - delim); - } else if(delim) { - user = url.substr(pos_p, pos_c - pos_p); - } - pos_p = pos_c + 1; - } - /* hostname[:port][/path] */ - pos_c = url.find_first_of(":/", pos_p); - if (pos_c == std::string::npos) { - /* only hostname, without post and path */ - host = url.substr(pos_p, std::string::npos); - return true; - } else if (url.at(pos_c) == ':') { - host = url.substr(pos_p, pos_c - pos_p); - /* port[/path] */ - pos_p = pos_c + 1; - pos_c = url.find('/', pos_p); - std::string port_str = (pos_c == std::string::npos) - ? url.substr(pos_p, std::string::npos) - : url.substr(pos_p, pos_c - pos_p); - /* stoi throws exception on failure, we don't need it */ - for (char c : port_str) { - if (c < '0' || c > '9') - return false; - port *= 10; - port += c - '0'; - } - if (pos_c == std::string::npos) - return true; /* no path part */ - pos_p = pos_c; - } else { - /* start of path part found */ - host = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c; - } - } - - /* pos_p now at start of path part */ - pos_c = url.find_first_of("?#", pos_p); - if (pos_c == std::string::npos) { - /* only path, without fragment and query */ - path = url.substr(pos_p, std::string::npos); - return true; - } else if (url.at(pos_c) == '?') { - /* found query part */ - path = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - pos_c = url.find('#', pos_p); - if (pos_c == std::string::npos) { - /* no fragment */ - query = url.substr(pos_p, std::string::npos); - return true; - } else { - query = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - } - } else { - /* found fragment part */ - path = url.substr(pos_p, pos_c - pos_p); - pos_p = pos_c + 1; - } - - /* pos_p now at start of fragment part */ - frag = url.substr(pos_p, std::string::npos); - return true; - } - - bool URL::parse_query(std::map & params) { - std::vector tokens; - strsplit(query, tokens, '&'); - - params.clear(); - for (const auto& it : tokens) { - std::size_t eq = it.find ('='); - if (eq != std::string::npos) { - auto e = std::pair(it.substr(0, eq), it.substr(eq + 1)); - params.insert(e); - } else { - auto e = std::pair(it, ""); - params.insert(e); - } - } - return true; - } - - std::string URL::to_string() { - std::string out = ""; - if (schema != "") { - out = schema + "://"; - if (user != "" && pass != "") { - out += user + ":" + pass + "@"; - } else if (user != "") { - out += user + "@"; - } - if (port) { - out += host + ":" + std::to_string(port); - } else { - out += host; - } - } - out += path; - if (query != "") - out += "?" + query; - if (frag != "") - out += "#" + frag; - return out; - } bool URL::is_i2p() const { return host.rfind(".i2p") == ( host.size() - 4 ); } - void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { - add_header(name, value.c_str(), replace); - } - - void HTTPMsg::add_header(const char *name, const char *value, bool replace) { - std::size_t count = headers.count(name); - if (count && !replace) - return; - if (count) { - headers[name] = value; - return; - } - headers.insert(std::pair(name, value)); - } - - void HTTPMsg::del_header(const char *name) { - headers.erase(name); - } - - int HTTPReq::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); - } - - int HTTPReq::parse(const std::string& str) { - enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE; - std::size_t eoh = str.find(HTTP_EOH); /* request head size */ - std::size_t eol = 0, pos = 0; - URL url; - - if (eoh == std::string::npos) - return 0; /* str not contains complete request */ - - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == REQ_LINE) { - std::string line = str.substr(pos, eol - pos); - std::vector tokens; - strsplit(line, tokens, ' '); - if (tokens.size() != 3) - return -1; - if (!is_http_method(tokens[0])) - return -1; - if (!is_http_version(tokens[2])) - return -1; - if (!url.parse(tokens[1])) - return -1; - /* all ok */ - method = tokens[0]; - uri = tokens[1]; - version = tokens[2]; - expect = HEADER_LINE; - } - else - { - std::string line = str.substr(pos, eol - pos); - auto p = parse_header_line(line); - if (p.first.length () > 0) - headers.push_back (p); - else - return -1; - } - pos = eol + strlen(CRLF); - if (pos >= eoh) - break; - } - return eoh + strlen(HTTP_EOH); - } - - void HTTPReq::write(std::ostream & o) - { - o << method << " " << uri << " " << version << CRLF; - for (auto & h : headers) - o << h.first << ": " << h.second << CRLF; - o << CRLF; - } + void HTTPMsg::add_header(const char *name, std::string & value, bool replace) { + add_header(name, value.c_str(), replace); + } + + void HTTPMsg::add_header(const char *name, const char *value, bool replace) { + std::size_t count = headers.count(name); + if (count && !replace) + return; + if (count) { + headers[name] = value; + return; + } + headers.insert(std::pair(name, value)); + } + + void HTTPMsg::del_header(const char *name) { + headers.erase(name); + } + + int HTTPReq::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); + } + + int HTTPReq::parse(const std::string& str) { + enum { REQ_LINE, HEADER_LINE } expect = REQ_LINE; + std::size_t eoh = str.find(HTTP_EOH); /* request head size */ + std::size_t eol = 0, pos = 0; + URL url; + + if (eoh == std::string::npos) + return 0; /* str not contains complete request */ + + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == REQ_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; + strsplit(line, tokens, ' '); + if (tokens.size() != 3) + return -1; + if (!is_http_method(tokens[0])) + return -1; + if (!is_http_version(tokens[2])) + return -1; + if (!url.parse(tokens[1])) + return -1; + /* all ok */ + method = tokens[0]; + uri = tokens[1]; + version = tokens[2]; + expect = HEADER_LINE; + } + else + { + std::string line = str.substr(pos, eol - pos); + auto p = parse_header_line(line); + if (p.first.length () > 0) + headers.push_back (p); + else + return -1; + } + pos = eol + strlen(CRLF); + if (pos >= eoh) + break; + } + return eoh + strlen(HTTP_EOH); + } + + void HTTPReq::write(std::ostream & o) + { + o << method << " " << uri << " " << version << CRLF; + for (auto & h : headers) + o << h.first << ": " << h.second << CRLF; + o << CRLF; + } std::string HTTPReq::to_string() { @@ -306,7 +307,7 @@ namespace http { void HTTPReq::UpdateHeader (const std::string& name, const std::string& value) { - for (auto& it : headers) + for (auto& it : headers) if (it.first == name) { it.second = value; @@ -327,177 +328,176 @@ namespace http { std::string HTTPReq::GetHeader (const std::string& name) const { - for (auto& it : headers) + for (auto& it : headers) if (it.first == name) return it.second; return ""; } - bool HTTPRes::is_chunked() const - { - auto it = headers.find("Transfer-Encoding"); - if (it == headers.end()) - return false; - if (it->second.find("chunked") == std::string::npos) - return true; - return false; - } - - bool HTTPRes::is_gzipped(bool includingI2PGzip) const - { - auto it = headers.find("Content-Encoding"); - if (it == headers.end()) - return false; /* no header */ - if (it->second.find("gzip") != std::string::npos) - return true; /* gotcha! */ - if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) - return true; - return false; - } - - long int HTTPMsg::content_length() const - { - unsigned long int length = 0; - auto it = headers.find("Content-Length"); - if (it == headers.end()) - return -1; - errno = 0; - length = std::strtoul(it->second.c_str(), (char **) NULL, 10); - if (errno != 0) - return -1; - return length; - } - - int HTTPRes::parse(const char *buf, size_t len) { - std::string str(buf, len); - return parse(str); - } - - int HTTPRes::parse(const std::string& str) { - enum { RES_LINE, HEADER_LINE } expect = RES_LINE; - std::size_t eoh = str.find(HTTP_EOH); /* request head size */ - std::size_t eol = 0, pos = 0; - - if (eoh == std::string::npos) - return 0; /* str not contains complete request */ - - while ((eol = str.find(CRLF, pos)) != std::string::npos) { - if (expect == RES_LINE) { - std::string line = str.substr(pos, eol - pos); - std::vector tokens; - strsplit(line, tokens, ' ', 3); - if (tokens.size() != 3) - return -1; - if (!is_http_version(tokens[0])) - return -1; - code = atoi(tokens[1].c_str()); - if (code < 100 || code >= 600) - return -1; - /* all ok */ - version = tokens[0]; - status = tokens[2]; - expect = HEADER_LINE; - } else { - std::string line = str.substr(pos, eol - pos); - auto p = parse_header_line(line); - if (p.first.length () > 0) - headers.insert (p); - else - return -1; - } - pos = eol + strlen(CRLF); - if (pos >= eoh) - break; - } - - return eoh + strlen(HTTP_EOH); - } - - std::string HTTPRes::to_string() { - if (version == "HTTP/1.1" && headers.count("Date") == 0) { - std::string date; - gen_rfc7231_date(date); - add_header("Date", date.c_str()); - } - if (status == "OK" && code != 200) - status = HTTPCodeToStatus(code); // update - if (body.length() > 0 && headers.count("Content-Length") == 0) - add_header("Content-Length", std::to_string(body.length()).c_str()); - /* build response */ - std::stringstream ss; - ss << version << " " << code << " " << status << CRLF; - for (auto & h : headers) { - ss << h.first << ": " << h.second << CRLF; - } - ss << CRLF; - if (body.length() > 0) - ss << body; - return ss.str(); - } - - const char * HTTPCodeToStatus(int code) { - const char *ptr; - switch (code) { - case 105: ptr = "Name Not Resolved"; break; - /* success */ - case 200: ptr = "OK"; break; - case 206: ptr = "Partial Content"; break; - /* redirect */ - case 301: ptr = "Moved Permanently"; break; - case 302: ptr = "Found"; break; - case 304: ptr = "Not Modified"; break; - case 307: ptr = "Temporary Redirect"; break; - /* client error */ - case 400: ptr = "Bad Request"; break; - case 401: ptr = "Unauthorized"; break; - case 403: ptr = "Forbidden"; break; - case 404: ptr = "Not Found"; break; - case 407: ptr = "Proxy Authentication Required"; break; - case 408: ptr = "Request Timeout"; break; - /* server error */ - case 500: ptr = "Internal Server Error"; break; - case 502: ptr = "Bad Gateway"; break; - case 503: ptr = "Not Implemented"; break; - case 504: ptr = "Gateway Timeout"; break; - default: ptr = "Unknown Status"; break; - } - return ptr; - } - - std::string UrlDecode(const std::string& data, bool allow_null) { + bool HTTPRes::is_chunked() const + { + auto it = headers.find("Transfer-Encoding"); + if (it == headers.end()) + return false; + if (it->second.find("chunked") == std::string::npos) + return true; + return false; + } + + bool HTTPRes::is_gzipped(bool includingI2PGzip) const + { + auto it = headers.find("Content-Encoding"); + if (it == headers.end()) + return false; /* no header */ + if (it->second.find("gzip") != std::string::npos) + return true; /* gotcha! */ + if (includingI2PGzip && it->second.find("x-i2p-gzip") != std::string::npos) + return true; + return false; + } + + long int HTTPMsg::content_length() const + { + unsigned long int length = 0; + auto it = headers.find("Content-Length"); + if (it == headers.end()) + return -1; + errno = 0; + length = std::strtoul(it->second.c_str(), (char **) NULL, 10); + if (errno != 0) + return -1; + return length; + } + + int HTTPRes::parse(const char *buf, size_t len) { + std::string str(buf, len); + return parse(str); + } + + int HTTPRes::parse(const std::string& str) { + enum { RES_LINE, HEADER_LINE } expect = RES_LINE; + std::size_t eoh = str.find(HTTP_EOH); /* request head size */ + std::size_t eol = 0, pos = 0; + + if (eoh == std::string::npos) + return 0; /* str not contains complete request */ + + while ((eol = str.find(CRLF, pos)) != std::string::npos) { + if (expect == RES_LINE) { + std::string line = str.substr(pos, eol - pos); + std::vector tokens; + strsplit(line, tokens, ' ', 3); + if (tokens.size() != 3) + return -1; + if (!is_http_version(tokens[0])) + return -1; + code = atoi(tokens[1].c_str()); + if (code < 100 || code >= 600) + return -1; + /* all ok */ + version = tokens[0]; + status = tokens[2]; + expect = HEADER_LINE; + } else { + std::string line = str.substr(pos, eol - pos); + auto p = parse_header_line(line); + if (p.first.length () > 0) + headers.insert (p); + else + return -1; + } + pos = eol + strlen(CRLF); + if (pos >= eoh) + break; + } + return eoh + strlen(HTTP_EOH); + } + + std::string HTTPRes::to_string() { + if (version == "HTTP/1.1" && headers.count("Date") == 0) { + std::string date; + gen_rfc7231_date(date); + add_header("Date", date.c_str()); + } + if (status == "OK" && code != 200) + status = HTTPCodeToStatus(code); // update + if (body.length() > 0 && headers.count("Content-Length") == 0) + add_header("Content-Length", std::to_string(body.length()).c_str()); + /* build response */ + std::stringstream ss; + ss << version << " " << code << " " << status << CRLF; + for (auto & h : headers) { + ss << h.first << ": " << h.second << CRLF; + } + ss << CRLF; + if (body.length() > 0) + ss << body; + return ss.str(); + } + + const char * HTTPCodeToStatus(int code) { + const char *ptr; + switch (code) { + case 105: ptr = "Name Not Resolved"; break; + /* success */ + case 200: ptr = "OK"; break; + case 206: ptr = "Partial Content"; break; + /* redirect */ + case 301: ptr = "Moved Permanently"; break; + case 302: ptr = "Found"; break; + case 304: ptr = "Not Modified"; break; + case 307: ptr = "Temporary Redirect"; break; + /* client error */ + case 400: ptr = "Bad Request"; break; + case 401: ptr = "Unauthorized"; break; + case 403: ptr = "Forbidden"; break; + case 404: ptr = "Not Found"; break; + case 407: ptr = "Proxy Authentication Required"; break; + case 408: ptr = "Request Timeout"; break; + /* server error */ + case 500: ptr = "Internal Server Error"; break; + case 502: ptr = "Bad Gateway"; break; + case 503: ptr = "Not Implemented"; break; + case 504: ptr = "Gateway Timeout"; break; + default: ptr = "Unknown Status"; break; + } + return ptr; + } + + std::string UrlDecode(const std::string& data, bool allow_null) { std::string decoded(data); - size_t pos = 0; - while ((pos = decoded.find('%', pos)) != std::string::npos) { - char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); - if (c == '\0' && !allow_null) { - pos += 3; - continue; - } - decoded.replace(pos, 3, 1, c); - pos++; - } - return decoded; - } - - bool MergeChunkedResponse (std::istream& in, std::ostream& out) { - std::string hexLen; - while (!in.eof ()) { - std::getline (in, hexLen); - errno = 0; - long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); - if (errno != 0) - return false; /* conversion error */ - if (len == 0) - return true; /* end of stream */ - if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */ - return false; /* too large chunk */ - char * buf = new char[len]; - in.read (buf, len); - out.write (buf, len); - delete[] buf; - std::getline (in, hexLen); // read \r\n after chunk - } - return true; - } + size_t pos = 0; + while ((pos = decoded.find('%', pos)) != std::string::npos) { + char c = strtol(decoded.substr(pos + 1, 2).c_str(), NULL, 16); + if (c == '\0' && !allow_null) { + pos += 3; + continue; + } + decoded.replace(pos, 3, 1, c); + pos++; + } + return decoded; + } + + bool MergeChunkedResponse (std::istream& in, std::ostream& out) { + std::string hexLen; + while (!in.eof ()) { + std::getline (in, hexLen); + errno = 0; + long int len = strtoul(hexLen.c_str(), (char **) NULL, 16); + if (errno != 0) + return false; /* conversion error */ + if (len == 0) + return true; /* end of stream */ + if (len < 0 || len > 10 * 1024 * 1024) /* < 10Mb */ + return false; /* too large chunk */ + char * buf = new char[len]; + in.read (buf, len); + out.write (buf, len); + delete[] buf; + std::getline (in, hexLen); // read \r\n after chunk + } + return true; + } } // http } // i2p diff --git a/libi2pd/HTTP.h b/libi2pd/HTTP.h index 837faf01..f156fef0 100644 --- a/libi2pd/HTTP.h +++ b/libi2pd/HTTP.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2013-2016, The PurpleI2P Project +* Copyright (c) 2013-2019, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * @@ -20,152 +20,152 @@ namespace i2p { namespace http { - const char CRLF[] = "\r\n"; /**< HTTP line terminator */ - const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ - extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ - extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ - - struct URL - { - std::string schema; - std::string user; - std::string pass; - std::string host; - unsigned short int port; - std::string path; - std::string query; - std::string frag; - - URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; - - /** - * @brief Tries to parse url from string - * @return true on success, false on invalid url - */ - bool parse (const char *str, std::size_t len = 0); - bool parse (const std::string& url); - - /** - * @brief Parse query part of url to key/value map - * @note Honestly, this should be implemented with std::multimap - */ - bool parse_query(std::map & params); - - /** - * @brief Serialize URL structure to url - * @note Returns relative url if schema if empty, absolute url otherwise - */ - std::string to_string (); - - /** - * @brief return true if the host is inside i2p - */ - bool is_i2p() const; - }; - - struct HTTPMsg - { - std::map headers; - - void add_header(const char *name, std::string & value, bool replace = false); - void add_header(const char *name, const char *value, bool replace = false); - void del_header(const char *name); - - /** @brief Returns declared message length or -1 if unknown */ - long int content_length() const; - }; - - struct HTTPReq - { - std::list > headers; - std::string version; - std::string method; - std::string uri; - - HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; - - /** - * @brief Tries to parse HTTP request from string - * @return -1 on error, 0 on incomplete query, >0 on success - * @note Positive return value is a size of header - */ - int parse(const char *buf, size_t len); - int parse(const std::string& buf); - - /** @brief Serialize HTTP request to string */ - std::string to_string(); + const char CRLF[] = "\r\n"; /**< HTTP line terminator */ + const char HTTP_EOH[] = "\r\n\r\n"; /**< HTTP end-of-headers mark */ + extern const std::vector HTTP_METHODS; /**< list of valid HTTP methods */ + extern const std::vector HTTP_VERSIONS; /**< list of valid HTTP versions */ + + struct URL + { + std::string schema; + std::string user; + std::string pass; + std::string host; + unsigned short int port; + std::string path; + std::string query; + std::string frag; + + URL(): schema(""), user(""), pass(""), host(""), port(0), path(""), query(""), frag("") {}; + + /** + * @brief Tries to parse url from string + * @return true on success, false on invalid url + */ + bool parse (const char *str, std::size_t len = 0); + bool parse (const std::string& url); + + /** + * @brief Parse query part of url to key/value map + * @note Honestly, this should be implemented with std::multimap + */ + bool parse_query(std::map & params); + + /** + * @brief Serialize URL structure to url + * @note Returns relative url if schema if empty, absolute url otherwise + */ + std::string to_string (); + + /** + * @brief return true if the host is inside i2p + */ + bool is_i2p() const; + }; + + struct HTTPMsg + { + std::map headers; + + void add_header(const char *name, std::string & value, bool replace = false); + void add_header(const char *name, const char *value, bool replace = false); + void del_header(const char *name); + + /** @brief Returns declared message length or -1 if unknown */ + long int content_length() const; + }; + + struct HTTPReq + { + std::list > headers; + std::string version; + std::string method; + std::string uri; + + HTTPReq (): version("HTTP/1.0"), method("GET"), uri("/") {}; + + /** + * @brief Tries to parse HTTP request from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); + + /** @brief Serialize HTTP request to string */ + std::string to_string(); void write(std::ostream & o); - void AddHeader (const std::string& name, const std::string& value); - void UpdateHeader (const std::string& name, const std::string& value); - void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt - void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; - std::string GetHeader (const std::string& name) const; - }; - - struct HTTPRes : HTTPMsg { - std::string version; - std::string status; - unsigned short int code; - /** - * @brief Simplifies response generation - * - * If this variable is set, on @a to_string() call: - * * Content-Length header will be added if missing, - * * contents of @a body will be included in generated response - */ - std::string body; - - HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} - - /** - * @brief Tries to parse HTTP response from string - * @return -1 on error, 0 on incomplete query, >0 on success - * @note Positive return value is a size of header - */ - int parse(const char *buf, size_t len); - int parse(const std::string& buf); - - /** - * @brief Serialize HTTP response to string - * @note If @a version is set to HTTP/1.1, and Date header is missing, - * it will be generated based on current time and added to headers - * @note If @a body is set and Content-Length header is missing, - * this header will be added, based on body's length - */ - std::string to_string(); + void AddHeader (const std::string& name, const std::string& value); + void UpdateHeader (const std::string& name, const std::string& value); + void RemoveHeader (const std::string& name, const std::string& exempt); // remove all headers starting with name, but exempt + void RemoveHeader (const std::string& name) { RemoveHeader (name, ""); }; + std::string GetHeader (const std::string& name) const; + }; + + struct HTTPRes : HTTPMsg { + std::string version; + std::string status; + unsigned short int code; + /** + * @brief Simplifies response generation + * + * If this variable is set, on @a to_string() call: + * * Content-Length header will be added if missing, + * * contents of @a body will be included in generated response + */ + std::string body; + + HTTPRes (): version("HTTP/1.1"), status("OK"), code(200) {} + + /** + * @brief Tries to parse HTTP response from string + * @return -1 on error, 0 on incomplete query, >0 on success + * @note Positive return value is a size of header + */ + int parse(const char *buf, size_t len); + int parse(const std::string& buf); + + /** + * @brief Serialize HTTP response to string + * @note If @a version is set to HTTP/1.1, and Date header is missing, + * it will be generated based on current time and added to headers + * @note If @a body is set and Content-Length header is missing, + * this header will be added, based on body's length + */ + std::string to_string(); void write(std::ostream & o); - /** @brief Checks that response declared as chunked data */ - bool is_chunked() const ; - - /** @brief Checks that response contains compressed data */ - bool is_gzipped(bool includingI2PGzip = true) const; - }; - - /** - * @brief returns HTTP status string by integer code - * @param code HTTP code [100, 599] - * @return Immutable string with status - */ - const char * HTTPCodeToStatus(int code); - - /** - * @brief Replaces %-encoded characters in string with their values - * @param data Source string - * @param null If set to true - decode also %00 sequence, otherwise - skip - * @return Decoded string - */ - std::string UrlDecode(const std::string& data, bool null = false); - - /** - * @brief Merge HTTP response content with Transfer-Encoding: chunked - * @param in Input stream - * @param out Output stream - * @return true on success, false otherwise - */ - bool MergeChunkedResponse (std::istream& in, std::ostream& out); + /** @brief Checks that response declared as chunked data */ + bool is_chunked() const ; + + /** @brief Checks that response contains compressed data */ + bool is_gzipped(bool includingI2PGzip = true) const; + }; + + /** + * @brief returns HTTP status string by integer code + * @param code HTTP code [100, 599] + * @return Immutable string with status + */ + const char * HTTPCodeToStatus(int code); + + /** + * @brief Replaces %-encoded characters in string with their values + * @param data Source string + * @param null If set to true - decode also %00 sequence, otherwise - skip + * @return Decoded string + */ + std::string UrlDecode(const std::string& data, bool null = false); + + /** + * @brief Merge HTTP response content with Transfer-Encoding: chunked + * @param in Input stream + * @param out Output stream + * @return true on success, false otherwise + */ + bool MergeChunkedResponse (std::istream& in, std::ostream& out); } // http } // i2p diff --git a/libi2pd_client/HTTPProxy.cpp b/libi2pd_client/HTTPProxy.cpp index df4895a4..af4d2913 100644 --- a/libi2pd_client/HTTPProxy.cpp +++ b/libi2pd_client/HTTPProxy.cpp @@ -1,3 +1,11 @@ +/* +* Copyright (c) 2013-2019, The PurpleI2P Project +* +* This file is part of Purple i2pd project and licensed under BSD3 +* +* See full license text in LICENSE file at top of project tree +*/ + #include #include #include @@ -5,6 +13,7 @@ #include #include #include +#include #include #include "I2PService.h" @@ -211,16 +220,28 @@ namespace proxy { void HTTPReqHandler::SanitizeHTTPRequest(i2p::http::HTTPReq & req) { /* drop common headers */ - req.RemoveHeader("Referrer"); req.RemoveHeader("Via"); req.RemoveHeader("From"); req.RemoveHeader("Forwarded"); req.RemoveHeader("Accept", "Accept-Encoding"); // Accept*, but Accept-Encoding /* drop proxy-disclosing headers */ req.RemoveHeader("X-Forwarded"); - req.RemoveHeader("Proxy-"); // Proxy-* + req.RemoveHeader("Proxy-"); // Proxy-* /* replace headers */ req.UpdateHeader("User-Agent", "MYOB/6.66 (AN/ON)"); + + /** + * according to i2p ticket #1862: + * leave Referrer if requested URL with same schema, host and port, + * otherwise, drop it. + */ + if(req.GetHeader("Referrer") != "") { + i2p::http::URL reqURL; reqURL.parse(req.uri); + i2p::http::URL refURL; refURL.parse(req.GetHeader("Referrer")); + if(!boost::iequals(reqURL.schema, refURL.schema) || !boost::iequals(reqURL.host, refURL.host) || reqURL.port != refURL.port) + req.RemoveHeader("Referrer"); + } + /* add headers */ /* close connection, if not Connection: (U|u)pgrade (for websocket) */ auto h = req.GetHeader ("Connection");