diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/http/misc | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/http/misc')
-rw-r--r-- | library/cpp/http/misc/http_headers.h | 72 | ||||
-rw-r--r-- | library/cpp/http/misc/httpcodes.cpp | 141 | ||||
-rw-r--r-- | library/cpp/http/misc/httpcodes.h | 94 | ||||
-rw-r--r-- | library/cpp/http/misc/httpdate.cpp | 83 | ||||
-rw-r--r-- | library/cpp/http/misc/httpdate.h | 21 | ||||
-rw-r--r-- | library/cpp/http/misc/httpdate_ut.cpp | 15 | ||||
-rw-r--r-- | library/cpp/http/misc/httpreqdata.cpp | 196 | ||||
-rw-r--r-- | library/cpp/http/misc/httpreqdata.h | 125 | ||||
-rw-r--r-- | library/cpp/http/misc/httpreqdata_ut.cpp | 154 | ||||
-rw-r--r-- | library/cpp/http/misc/parsed_request.cpp | 32 | ||||
-rw-r--r-- | library/cpp/http/misc/parsed_request.h | 26 | ||||
-rw-r--r-- | library/cpp/http/misc/parsed_request_ut.cpp | 28 | ||||
-rw-r--r-- | library/cpp/http/misc/ut/ya.make | 11 | ||||
-rw-r--r-- | library/cpp/http/misc/ya.make | 24 |
14 files changed, 1022 insertions, 0 deletions
diff --git a/library/cpp/http/misc/http_headers.h b/library/cpp/http/misc/http_headers.h new file mode 100644 index 0000000000..ff359937fa --- /dev/null +++ b/library/cpp/http/misc/http_headers.h @@ -0,0 +1,72 @@ +#pragma once + +#include <util/generic/strbuf.h> + + +/* Taken from SpringFramework's HttpHeaders. Docs: + * https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/HttpHeaders.html + * Source: + * https://github.com/spring-projects/spring-framework/blob/816bbee8de584676250e2bc5dcff6da6cd81623f/spring-web/src/main/java/org/springframework/http/HttpHeaders.java + */ +namespace NHttpHeaders { + constexpr TStringBuf ACCEPT = "Accept"; + constexpr TStringBuf ACCEPT_CHARSET = "Accept-Charset"; + constexpr TStringBuf ACCEPT_ENCODING = "Accept-Encoding"; + constexpr TStringBuf ACCEPT_LANGUAGE = "Accept-Language"; + constexpr TStringBuf ACCEPT_RANGES = "Accept-Ranges"; + constexpr TStringBuf ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials"; + constexpr TStringBuf ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers"; + constexpr TStringBuf ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods"; + constexpr TStringBuf ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; + constexpr TStringBuf ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"; + constexpr TStringBuf ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + constexpr TStringBuf ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers"; + constexpr TStringBuf ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method"; + constexpr TStringBuf AGE = "Age"; + constexpr TStringBuf ALLOW = "Allow"; + constexpr TStringBuf AUTHORIZATION = "Authorization"; + constexpr TStringBuf CACHE_CONTROL = "Cache-Control"; + constexpr TStringBuf CONNECTION = "Connection"; + constexpr TStringBuf CONTENT_ENCODING = "Content-Encoding"; + constexpr TStringBuf CONTENT_DISPOSITION = "Content-Disposition"; + constexpr TStringBuf CONTENT_LANGUAGE = "Content-Language"; + constexpr TStringBuf CONTENT_LENGTH = "Content-Length"; + constexpr TStringBuf CONTENT_LOCATION = "Content-Location"; + constexpr TStringBuf CONTENT_RANGE = "Content-Range"; + constexpr TStringBuf CONTENT_TYPE = "Content-Type"; + constexpr TStringBuf COOKIE = "Cookie"; + constexpr TStringBuf DATE = "Date"; + constexpr TStringBuf ETAG = "ETag"; + constexpr TStringBuf EXPECT = "Expect"; + constexpr TStringBuf EXPIRES = "Expires"; + constexpr TStringBuf FROM = "From"; + constexpr TStringBuf HOST = "Host"; + constexpr TStringBuf IF_MATCH = "If-Match"; + constexpr TStringBuf IF_MODIFIED_SINCE = "If-Modified-Since"; + constexpr TStringBuf IF_NONE_MATCH = "If-None-Match"; + constexpr TStringBuf IF_RANGE = "If-Range"; + constexpr TStringBuf IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + constexpr TStringBuf LAST_MODIFIED = "Last-Modified"; + constexpr TStringBuf LINK = "Link"; + constexpr TStringBuf LOCATION = "Location"; + constexpr TStringBuf MAX_FORWARDS = "Max-Forwards"; + constexpr TStringBuf ORIGIN = "Origin"; + constexpr TStringBuf PRAGMA = "Pragma"; + constexpr TStringBuf PROXY_AUTHENTICATE = "Proxy-Authenticate"; + constexpr TStringBuf PROXY_AUTHORIZATION = "Proxy-Authorization"; + constexpr TStringBuf RANGE = "Range"; + constexpr TStringBuf REFERER = "Referer"; + constexpr TStringBuf RETRY_AFTER = "Retry-After"; + constexpr TStringBuf SERVER = "Server"; + constexpr TStringBuf SET_COOKIE = "Set-Cookie"; + constexpr TStringBuf SET_COOKIE2 = "Set-Cookie2"; + constexpr TStringBuf TE = "TE"; + constexpr TStringBuf TRAILER = "Trailer"; + constexpr TStringBuf TRANSFER_ENCODING = "Transfer-Encoding"; + constexpr TStringBuf UPGRADE = "Upgrade"; + constexpr TStringBuf USER_AGENT = "User-Agent"; + constexpr TStringBuf VARY = "Vary"; + constexpr TStringBuf VIA = "Via"; + constexpr TStringBuf WARNING = "Warning"; + constexpr TStringBuf WWW_AUTHENTICATE = "WWW-Authenticate"; +} // namespace HttpHeaders diff --git a/library/cpp/http/misc/httpcodes.cpp b/library/cpp/http/misc/httpcodes.cpp new file mode 100644 index 0000000000..ad8c80ac1e --- /dev/null +++ b/library/cpp/http/misc/httpcodes.cpp @@ -0,0 +1,141 @@ +#include "httpcodes.h" + +TStringBuf HttpCodeStrEx(int code) noexcept { + switch (code) { + case HTTP_CONTINUE: + return TStringBuf("100 Continue"); + case HTTP_SWITCHING_PROTOCOLS: + return TStringBuf("101 Switching protocols"); + case HTTP_PROCESSING: + return TStringBuf("102 Processing"); + + case HTTP_OK: + return TStringBuf("200 Ok"); + case HTTP_CREATED: + return TStringBuf("201 Created"); + case HTTP_ACCEPTED: + return TStringBuf("202 Accepted"); + case HTTP_NON_AUTHORITATIVE_INFORMATION: + return TStringBuf("203 None authoritative information"); + case HTTP_NO_CONTENT: + return TStringBuf("204 No content"); + case HTTP_RESET_CONTENT: + return TStringBuf("205 Reset content"); + case HTTP_PARTIAL_CONTENT: + return TStringBuf("206 Partial content"); + case HTTP_MULTI_STATUS: + return TStringBuf("207 Multi status"); + case HTTP_ALREADY_REPORTED: + return TStringBuf("208 Already reported"); + case HTTP_IM_USED: + return TStringBuf("226 IM used"); + + case HTTP_MULTIPLE_CHOICES: + return TStringBuf("300 Multiple choices"); + case HTTP_MOVED_PERMANENTLY: + return TStringBuf("301 Moved permanently"); + case HTTP_FOUND: + return TStringBuf("302 Moved temporarily"); + case HTTP_SEE_OTHER: + return TStringBuf("303 See other"); + case HTTP_NOT_MODIFIED: + return TStringBuf("304 Not modified"); + case HTTP_USE_PROXY: + return TStringBuf("305 Use proxy"); + case HTTP_TEMPORARY_REDIRECT: + return TStringBuf("307 Temporarily redirect"); + case HTTP_PERMANENT_REDIRECT: + return TStringBuf("308 Permanent redirect"); + + case HTTP_BAD_REQUEST: + return TStringBuf("400 Bad request"); + case HTTP_UNAUTHORIZED: + return TStringBuf("401 Unauthorized"); + case HTTP_PAYMENT_REQUIRED: + return TStringBuf("402 Payment required"); + case HTTP_FORBIDDEN: + return TStringBuf("403 Forbidden"); + case HTTP_NOT_FOUND: + return TStringBuf("404 Not found"); + case HTTP_METHOD_NOT_ALLOWED: + return TStringBuf("405 Method not allowed"); + case HTTP_NOT_ACCEPTABLE: + return TStringBuf("406 Not acceptable"); + case HTTP_PROXY_AUTHENTICATION_REQUIRED: + return TStringBuf("407 Proxy Authentication required"); + case HTTP_REQUEST_TIME_OUT: + return TStringBuf("408 Request time out"); + case HTTP_CONFLICT: + return TStringBuf("409 Conflict"); + case HTTP_GONE: + return TStringBuf("410 Gone"); + case HTTP_LENGTH_REQUIRED: + return TStringBuf("411 Length required"); + case HTTP_PRECONDITION_FAILED: + return TStringBuf("412 Precondition failed"); + case HTTP_REQUEST_ENTITY_TOO_LARGE: + return TStringBuf("413 Request entity too large"); + case HTTP_REQUEST_URI_TOO_LARGE: + return TStringBuf("414 Request uri too large"); + case HTTP_UNSUPPORTED_MEDIA_TYPE: + return TStringBuf("415 Unsupported media type"); + case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: + return TStringBuf("416 Requested Range Not Satisfiable"); + case HTTP_EXPECTATION_FAILED: + return TStringBuf("417 Expectation Failed"); + case HTTP_I_AM_A_TEAPOT: + return TStringBuf("418 I Am A Teapot"); + case HTTP_AUTHENTICATION_TIMEOUT: + return TStringBuf("419 Authentication Timeout"); + case HTTP_MISDIRECTED_REQUEST: + return TStringBuf("421 Misdirected Request"); + case HTTP_UNPROCESSABLE_ENTITY: + return TStringBuf("422 Unprocessable Entity"); + case HTTP_LOCKED: + return TStringBuf("423 Locked"); + case HTTP_FAILED_DEPENDENCY: + return TStringBuf("424 Failed Dependency"); + case HTTP_UNORDERED_COLLECTION: + return TStringBuf("425 Unordered Collection"); + case HTTP_UPGRADE_REQUIRED: + return TStringBuf("426 Upgrade Required"); + case HTTP_PRECONDITION_REQUIRED: + return TStringBuf("428 Precondition Required"); + case HTTP_TOO_MANY_REQUESTS: + return TStringBuf("429 Too Many Requests"); + case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE: + return TStringBuf("431 Request Header Fields Too Large"); + case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: + return TStringBuf("451 Unavailable For Legal Reason"); + + case HTTP_INTERNAL_SERVER_ERROR: + return TStringBuf("500 Internal server error"); + case HTTP_NOT_IMPLEMENTED: + return TStringBuf("501 Not implemented"); + case HTTP_BAD_GATEWAY: + return TStringBuf("502 Bad gateway"); + case HTTP_SERVICE_UNAVAILABLE: + return TStringBuf("503 Service unavailable"); + case HTTP_GATEWAY_TIME_OUT: + return TStringBuf("504 Gateway time out"); + case HTTP_HTTP_VERSION_NOT_SUPPORTED: + return TStringBuf("505 HTTP version not supported"); + case HTTP_VARIANT_ALSO_NEGOTIATES: + return TStringBuf("506 Variant also negotiates"); + case HTTP_INSUFFICIENT_STORAGE: + return TStringBuf("507 Insufficient storage"); + case HTTP_LOOP_DETECTED: + return TStringBuf("508 Loop Detected"); + case HTTP_BANDWIDTH_LIMIT_EXCEEDED: + return TStringBuf("509 Bandwidth Limit Exceeded"); + case HTTP_NOT_EXTENDED: + return TStringBuf("510 Not Extended"); + case HTTP_NETWORK_AUTHENTICATION_REQUIRED: + return TStringBuf("511 Network Authentication Required"); + case HTTP_UNASSIGNED_512: + return TStringBuf("512 Unassigned"); + + default: + return TStringBuf("000 Unknown HTTP code"); + } +} diff --git a/library/cpp/http/misc/httpcodes.h b/library/cpp/http/misc/httpcodes.h new file mode 100644 index 0000000000..cbfbaa1188 --- /dev/null +++ b/library/cpp/http/misc/httpcodes.h @@ -0,0 +1,94 @@ +#pragma once + +#include <util/generic/strbuf.h> + +enum HttpCodes { + HTTP_CONTINUE = 100, + HTTP_SWITCHING_PROTOCOLS = 101, + HTTP_PROCESSING = 102, + + HTTP_OK = 200, + HTTP_CREATED = 201, + HTTP_ACCEPTED = 202, + HTTP_NON_AUTHORITATIVE_INFORMATION = 203, + HTTP_NO_CONTENT = 204, + HTTP_RESET_CONTENT = 205, + HTTP_PARTIAL_CONTENT = 206, + HTTP_MULTI_STATUS = 207, + HTTP_ALREADY_REPORTED = 208, + HTTP_IM_USED = 226, + + HTTP_MULTIPLE_CHOICES = 300, + HTTP_MOVED_PERMANENTLY = 301, + HTTP_FOUND = 302, + HTTP_SEE_OTHER = 303, + HTTP_NOT_MODIFIED = 304, + HTTP_USE_PROXY = 305, + HTTP_TEMPORARY_REDIRECT = 307, + HTTP_PERMANENT_REDIRECT = 308, + + HTTP_BAD_REQUEST = 400, + HTTP_UNAUTHORIZED = 401, + HTTP_PAYMENT_REQUIRED = 402, + HTTP_FORBIDDEN = 403, + HTTP_NOT_FOUND = 404, + HTTP_METHOD_NOT_ALLOWED = 405, + HTTP_NOT_ACCEPTABLE = 406, + HTTP_PROXY_AUTHENTICATION_REQUIRED = 407, + HTTP_REQUEST_TIME_OUT = 408, + HTTP_CONFLICT = 409, + HTTP_GONE = 410, + HTTP_LENGTH_REQUIRED = 411, + HTTP_PRECONDITION_FAILED = 412, + HTTP_REQUEST_ENTITY_TOO_LARGE = 413, + HTTP_REQUEST_URI_TOO_LARGE = 414, + HTTP_UNSUPPORTED_MEDIA_TYPE = 415, + HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416, + HTTP_EXPECTATION_FAILED = 417, + HTTP_I_AM_A_TEAPOT = 418, + HTTP_AUTHENTICATION_TIMEOUT = 419, + HTTP_MISDIRECTED_REQUEST = 421, + HTTP_UNPROCESSABLE_ENTITY = 422, + HTTP_LOCKED = 423, + HTTP_FAILED_DEPENDENCY = 424, + HTTP_UNORDERED_COLLECTION = 425, + HTTP_UPGRADE_REQUIRED = 426, + HTTP_PRECONDITION_REQUIRED = 428, + HTTP_TOO_MANY_REQUESTS = 429, + HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431, + HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451, + + HTTP_INTERNAL_SERVER_ERROR = 500, + HTTP_NOT_IMPLEMENTED = 501, + HTTP_BAD_GATEWAY = 502, + HTTP_SERVICE_UNAVAILABLE = 503, + HTTP_GATEWAY_TIME_OUT = 504, + HTTP_HTTP_VERSION_NOT_SUPPORTED = 505, + HTTP_VARIANT_ALSO_NEGOTIATES = 506, + HTTP_INSUFFICIENT_STORAGE = 507, + HTTP_LOOP_DETECTED = 508, + HTTP_BANDWIDTH_LIMIT_EXCEEDED = 509, + HTTP_NOT_EXTENDED = 510, + HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511, + HTTP_UNASSIGNED_512 = 512, + + HTTP_CODE_MAX +}; + +TStringBuf HttpCodeStrEx(int code) noexcept; + +inline TStringBuf HttpCodeStr(int code) noexcept { + return HttpCodeStrEx(code).Skip(4); +} + +inline bool IsHttpCode(int code) noexcept { + return HttpCodeStrEx(code).data() != HttpCodeStrEx(0).data(); +} + +inline bool IsUserError(int code) noexcept { + return code >= 400 && code < 500; +} + +inline bool IsServerError(int code) noexcept { + return code >= 500; +} diff --git a/library/cpp/http/misc/httpdate.cpp b/library/cpp/http/misc/httpdate.cpp new file mode 100644 index 0000000000..4a3031bbf4 --- /dev/null +++ b/library/cpp/http/misc/httpdate.cpp @@ -0,0 +1,83 @@ +/*- +* Copyright 1997 Massachusetts Institute of Technology +* +* Permission to use, copy, modify, and distribute this software and +* its documentation for any purpose and without fee is hereby +* granted, provided that both the above copyright notice and this +* permission notice appear in all copies, that both the above +* copyright notice and this permission notice appear in all +* supporting documentation, and that the name of M.I.T. not be used +* in advertising or publicity pertaining to distribution of the +* software without specific, written prior permission. M.I.T. makes +* no representations about the suitability of this software for any +* purpose. It is provided "as is" without express or implied +* warranty. +* +* THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS +* ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT +* SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +*/ +#include <util/system/defaults.h> + +#include <sys/types.h> +#include <cctype> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> + +#include <util/system/compat.h> /* stricmp */ +#include <util/system/yassert.h> +#include "httpdate.h" +#include <util/datetime/base.h> + +static const char *wkdays[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +static const char *months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", + "Nov", "Dec" +}; + +int format_http_date(char buf[], size_t size, time_t when) { + struct tm tms; + GmTimeR(&when, &tms); + +#ifndef HTTP_DATE_ISO_8601 + return snprintf(buf, size, "%s, %02d %s %04d %02d:%02d:%02d GMT", + wkdays[tms.tm_wday], tms.tm_mday, months[tms.tm_mon], + tms.tm_year + 1900, tms.tm_hour, tms.tm_min, tms.tm_sec); +#else /* ISO 8601 */ + return snprintf(buf, size, "%04d%02d%02dT%02d%02d%02d+0000", + tms.tm_year + 1900, tms.tm_mon + 1, tms.tm_mday, + tms.tm_hour, tms.tm_min, tms.tm_sec); +#endif +} + +char* format_http_date(time_t when, char* buf, size_t buflen) { + const int len = format_http_date(buf, buflen, when); + + if (len == 0) { + return nullptr; + } + + Y_ASSERT(len > 0 && size_t(len) < buflen); + + return buf; +} + +TString FormatHttpDate(time_t when) { + char str[64] = {0}; + format_http_date(str, Y_ARRAY_SIZE(str), when); + return TString(str); +} diff --git a/library/cpp/http/misc/httpdate.h b/library/cpp/http/misc/httpdate.h new file mode 100644 index 0000000000..04876f38fe --- /dev/null +++ b/library/cpp/http/misc/httpdate.h @@ -0,0 +1,21 @@ +#pragma once + +#include <util/datetime/base.h> +#include <util/generic/string.h> + +#include <ctime> + +#define BAD_DATE ((time_t)-1) + +inline time_t parse_http_date(const TStringBuf& datestring) { + try { + return TInstant::ParseHttpDeprecated(datestring).TimeT(); + } catch (const TDateTimeParseException&) { + return BAD_DATE; + } +} + +int format_http_date(char buf[], size_t size, time_t when); +char* format_http_date(time_t when, char* buf, size_t len); + +TString FormatHttpDate(time_t when); diff --git a/library/cpp/http/misc/httpdate_ut.cpp b/library/cpp/http/misc/httpdate_ut.cpp new file mode 100644 index 0000000000..c1a0103501 --- /dev/null +++ b/library/cpp/http/misc/httpdate_ut.cpp @@ -0,0 +1,15 @@ +#include <library/cpp/testing/unittest/registar.h> + +#include "httpdate.h" + +Y_UNIT_TEST_SUITE(TestHttpDate) { + Y_UNIT_TEST(Test1) { + char buf1[100]; + char buf2[100]; + + UNIT_ASSERT((int)strlen(format_http_date(0, buf1, sizeof(buf1))) == format_http_date(buf2, sizeof(buf2), 0)); + } + Y_UNIT_TEST(Test2) { + UNIT_ASSERT_STRINGS_EQUAL(FormatHttpDate(1234567890), "Fri, 13 Feb 2009 23:31:30 GMT"); + } +} diff --git a/library/cpp/http/misc/httpreqdata.cpp b/library/cpp/http/misc/httpreqdata.cpp new file mode 100644 index 0000000000..f6951f68cd --- /dev/null +++ b/library/cpp/http/misc/httpreqdata.cpp @@ -0,0 +1,196 @@ +#include "httpreqdata.h" + +#include <util/stream/mem.h> + +TBaseServerRequestData::TBaseServerRequestData(SOCKET s) + : Addr(nullptr) + , Host() + , Port() + , Path(nullptr) + , Search(nullptr) + , SearchLength(0) + , Socket(s) + , BeginTime(MicroSeconds()) +{ +} + +TBaseServerRequestData::TBaseServerRequestData(const char* qs, SOCKET s) + : Addr(nullptr) + , Host() + , Port() + , Path(nullptr) + , Search((char*)qs) + , SearchLength(qs ? strlen(qs) : 0) + , OrigSearch(Search, SearchLength) + , Socket(s) + , BeginTime(MicroSeconds()) +{ +} + +void TBaseServerRequestData::AppendQueryString(const char* str, size_t length) { + if (Y_UNLIKELY(Search)) { + Y_ASSERT(strlen(Search) == SearchLength); + ModifiedQueryString.Reserve(SearchLength + length + 2); + ModifiedQueryString.Assign(Search, SearchLength); + if (SearchLength > 0 && Search[SearchLength - 1] != '&' && + length > 0 && str[0] != '&') { + ModifiedQueryString.Append('&'); + } + ModifiedQueryString.Append(str, length); + } else { + ModifiedQueryString.Reserve(length + 1); + ModifiedQueryString.Assign(str, length); + } + ModifiedQueryString.Append('\0'); + Search = ModifiedQueryString.data(); + SearchLength = ModifiedQueryString.size() - 1; // ignore terminator +} + +void TBaseServerRequestData::SetRemoteAddr(TStringBuf addr) { + TMemoryOutput out(AddrData, Y_ARRAY_SIZE(AddrData) - 1); + out.Write(addr.substr(0, Y_ARRAY_SIZE(AddrData) - 1)); + *out.Buf() = '\0'; + + Addr = AddrData; +} + +const char* TBaseServerRequestData::RemoteAddr() const { + if (!Addr) { + *AddrData = 0; + GetRemoteAddr(Socket, AddrData, sizeof(AddrData)); + Addr = AddrData; + } + + return Addr; +} + +const char* TBaseServerRequestData::HeaderIn(TStringBuf key) const { + auto it = HeadersIn_.find(key); + + if (it == HeadersIn_.end()) { + return nullptr; + } + + return it->second.data(); +} + +TString TBaseServerRequestData::HeaderByIndex(size_t n) const noexcept { + if (n >= HeadersCount()) { + return nullptr; + } + + THttpHeadersContainer::const_iterator i = HeadersIn_.begin(); + + while (n) { + ++i; + --n; + } + + return TString(i->first) + TStringBuf(": ") + i->second; +} + +const char* TBaseServerRequestData::Environment(const char* key) const { + if (stricmp(key, "REMOTE_ADDR") == 0) { + const char* ip = HeaderIn("X-Real-IP"); + if (ip) + return ip; + return RemoteAddr(); + } else if (stricmp(key, "QUERY_STRING") == 0) { + return QueryString(); + } else if (stricmp(key, "SERVER_NAME") == 0) { + return ServerName().data(); + } else if (stricmp(key, "SERVER_PORT") == 0) { + return ServerPort().data(); + } else if (stricmp(key, "SCRIPT_NAME") == 0) { + return ScriptName(); + } + return nullptr; +} + +void TBaseServerRequestData::Clear() { + HeadersIn_.clear(); + Addr = Path = Search = nullptr; + OrigSearch = {}; + SearchLength = 0; + Host.clear(); + Port.clear(); + CurPage.remove(); + ParseBuf.Clear(); + BeginTime = MicroSeconds(); +} + +const char* TBaseServerRequestData::GetCurPage() const { + if (!CurPage && Host) { + CurPage = "http://"; + CurPage += Host; + if (Port) { + CurPage += ':'; + CurPage += Port; + } + CurPage += Path; + if (Search) { + CurPage += '?'; + CurPage += Search; + } + } + return CurPage.data(); +} + +bool TBaseServerRequestData::Parse(const char* origReq) { + size_t origReqLength = strlen(origReq); + ParseBuf.Assign(origReq, origReqLength + 1); + char* req = ParseBuf.Data(); + + while (*req == ' ' || *req == '\t') + req++; + if (*req != '/') + return false; // we are not a proxy + while (req[1] == '/') // remove redundant slashes + req++; + + // detect url end (can contain some garbage after whitespace, e.g. 'HTTP 1.1') + char* urlEnd = req; + while (*urlEnd && *urlEnd != ' ' && *urlEnd != '\t') + urlEnd++; + if (*urlEnd) + *urlEnd = 0; + + // cut fragment if exists + char* fragment = strchr(req, '#'); + if (fragment) + *fragment = 0; // ignore fragment + else + fragment = urlEnd; + Path = req; + + // calculate Search length without additional strlen-ing + Search = strchr(Path, '?'); + if (Search) { + *Search++ = 0; + ptrdiff_t delta = fragment - Search; + // indeed, second case is a parse error + SearchLength = (delta >= 0) ? delta : (urlEnd - Search); + Y_ASSERT(strlen(Search) == SearchLength); + } else { + SearchLength = 0; + } + OrigSearch = {Search, SearchLength}; + + return true; +} + +void TBaseServerRequestData::AddHeader(const TString& name, const TString& value) { + HeadersIn_[name] = value; + + if (stricmp(name.data(), "Host") == 0) { + size_t hostLen = strcspn(value.data(), ":"); + if (value[hostLen] == ':') + Port = value.substr(hostLen + 1); + Host = value.substr(0, hostLen); + } +} + +void TBaseServerRequestData::SetPath(const TString& path) { + PathStorage = TBuffer(path.data(), path.size() + 1); + Path = PathStorage.Data(); +} diff --git a/library/cpp/http/misc/httpreqdata.h b/library/cpp/http/misc/httpreqdata.h new file mode 100644 index 0000000000..16e59c4d78 --- /dev/null +++ b/library/cpp/http/misc/httpreqdata.h @@ -0,0 +1,125 @@ +#pragma once + +#include <library/cpp/digest/lower_case/hash_ops.h> + +#include <util/str_stl.h> + +#include <util/system/defaults.h> +#include <util/string/cast.h> +#include <library/cpp/cgiparam/cgiparam.h> +#include <util/network/address.h> +#include <util/network/socket.h> +#include <util/generic/hash.h> +#include <util/system/yassert.h> +#include <util/generic/string.h> +#include <util/datetime/base.h> +#include <util/generic/buffer.h> + +using THttpHeadersContainer = THashMap<TString, TString, TCIOps, TCIOps>; + +class TBaseServerRequestData { +public: + TBaseServerRequestData(SOCKET s = INVALID_SOCKET); + TBaseServerRequestData(const char* qs, SOCKET s = INVALID_SOCKET); + + void SetHost(const TString& host, ui16 port) { + Host = host; + Port = ToString(port); + } + + const TString& ServerName() const { + return Host; + } + + NAddr::IRemoteAddrPtr ServerAddress() const { + return NAddr::GetSockAddr(Socket); + } + + const TString& ServerPort() const { + return Port; + } + + const char* ScriptName() const { + return Path; + } + + const char* QueryString() const { + return Search; + } + + TStringBuf QueryStringBuf() const { + return TStringBuf(Search, SearchLength); + } + + TStringBuf OrigQueryStringBuf() const { + return OrigSearch; + } + + void AppendQueryString(const char* str, size_t length); + const char* RemoteAddr() const; + void SetRemoteAddr(TStringBuf addr); + const char* HeaderIn(TStringBuf key) const; + + const THttpHeadersContainer& HeadersIn() const { + return HeadersIn_; + } + + inline size_t HeadersCount() const noexcept { + return HeadersIn_.size(); + } + + TString HeaderByIndex(size_t n) const noexcept; + const char* Environment(const char* key) const; + + void Clear(); + + void SetSocket(SOCKET s) noexcept { + Socket = s; + } + + ui64 RequestBeginTime() const noexcept { + return BeginTime; + } + + void SetPath(const TString& path); + const char* GetCurPage() const; + bool Parse(const char* req); + void AddHeader(const TString& name, const TString& value); + +private: + TBuffer PathStorage; + mutable char* Addr; + TString Host; + TString Port; + char* Path; + char* Search; + size_t SearchLength; // length of Search + TStringBuf OrigSearch; + THttpHeadersContainer HeadersIn_; + mutable char AddrData[INET6_ADDRSTRLEN]; + SOCKET Socket; + ui64 BeginTime; + mutable TString CurPage; + TBuffer ParseBuf; + TBuffer ModifiedQueryString; +}; + +class TServerRequestData: public TBaseServerRequestData { +public: + TServerRequestData(SOCKET s = INVALID_SOCKET) + : TBaseServerRequestData(s) + { + } + TServerRequestData(const char* qs, SOCKET s = INVALID_SOCKET) + : TBaseServerRequestData(qs, s) + { + Scan(); + } + + void Scan() { + CgiParam.Scan(QueryStringBuf()); + } + +public: + TCgiParameters CgiParam; +}; diff --git a/library/cpp/http/misc/httpreqdata_ut.cpp b/library/cpp/http/misc/httpreqdata_ut.cpp new file mode 100644 index 0000000000..e7f16ef27c --- /dev/null +++ b/library/cpp/http/misc/httpreqdata_ut.cpp @@ -0,0 +1,154 @@ +#include "httpreqdata.h" + +#include <library/cpp/testing/unittest/registar.h> + +Y_UNIT_TEST_SUITE(TRequestServerDataTest) { + Y_UNIT_TEST(Headers) { + TServerRequestData sd; + + sd.AddHeader("x-xx", "y-yy"); + sd.AddHeader("x-Xx", "y-yy"); + + UNIT_ASSERT_VALUES_EQUAL(sd.HeadersCount(), 1); + + sd.AddHeader("x-XxX", "y-yyy"); + UNIT_ASSERT_VALUES_EQUAL(sd.HeadersCount(), 2); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(sd.HeaderIn("X-XX")), TStringBuf("y-yy")); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(sd.HeaderIn("X-XXX")), TStringBuf("y-yyy")); + } + + Y_UNIT_TEST(ComplexHeaders) { + TServerRequestData sd; + sd.SetHost("zzz", 1); + + sd.AddHeader("x-Xx", "y-yy"); + UNIT_ASSERT_VALUES_EQUAL(sd.HeadersCount(), 1); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(sd.HeaderIn("X-XX")), TStringBuf("y-yy")); + + sd.AddHeader("x-Xz", "y-yy"); + UNIT_ASSERT_VALUES_EQUAL(sd.HeadersCount(), 2); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(sd.HeaderIn("X-Xz")), TStringBuf("y-yy")); + + UNIT_ASSERT_VALUES_EQUAL(sd.ServerName(), "zzz"); + UNIT_ASSERT_VALUES_EQUAL(sd.ServerPort(), "1"); + sd.AddHeader("Host", "1234"); + UNIT_ASSERT_VALUES_EQUAL(sd.HeadersCount(), 3); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(sd.HeaderIn("Host")), TStringBuf("1234")); + UNIT_ASSERT_VALUES_EQUAL(sd.ServerName(), "1234"); + sd.AddHeader("Host", "12345:678"); + UNIT_ASSERT_VALUES_EQUAL(sd.HeadersCount(), 3); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(sd.HeaderIn("Host")), TStringBuf("12345:678")); + UNIT_ASSERT_VALUES_EQUAL(sd.ServerName(), "12345"); + UNIT_ASSERT_VALUES_EQUAL(sd.ServerPort(), "678"); + } + + Y_UNIT_TEST(ParseScan) { + TServerRequestData rd; + + // Parse parses url without host + UNIT_ASSERT(!rd.Parse(" http://yandex.ru/yandsearch?>a=fake&haha=da HTTP 1.1 OK")); + + // This should work + UNIT_ASSERT(rd.Parse(" /yandsearch?>a=fake&haha=da HTTP 1.1 OK")); + + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), ">a=fake&haha=da"); + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), rd.OrigQueryStringBuf()); + + rd.Scan(); + UNIT_ASSERT(rd.CgiParam.Has("gta", "fake")); + UNIT_ASSERT(rd.CgiParam.Has("haha", "da")); + UNIT_ASSERT(!rd.CgiParam.Has("no-param")); + + rd.Clear(); + } + + Y_UNIT_TEST(Ctor) { + const TString qs("gta=fake&haha=da"); + TServerRequestData rd(qs.c_str()); + + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), qs); + UNIT_ASSERT_STRINGS_EQUAL(rd.OrigQueryStringBuf(), qs); + + UNIT_ASSERT(rd.CgiParam.Has("gta")); + UNIT_ASSERT(rd.CgiParam.Has("haha")); + UNIT_ASSERT(!rd.CgiParam.Has("no-param")); + } + + Y_UNIT_TEST(HashCut) { + const TString qs(">a=fake&haha=da"); + const TString header = " /yandsearch?" + qs + "#&uberParam=yes&q=? HTTP 1.1 OK"; + + TServerRequestData rd; + rd.Parse(header.c_str()); + + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), qs); + UNIT_ASSERT_STRINGS_EQUAL(rd.OrigQueryStringBuf(), qs); + + rd.Scan(); + UNIT_ASSERT(rd.CgiParam.Has("gta")); + UNIT_ASSERT(rd.CgiParam.Has("haha")); + UNIT_ASSERT(!rd.CgiParam.Has("uberParam")); + } + + Y_UNIT_TEST(MisplacedHashCut) { + TServerRequestData rd; + rd.Parse(" /y#ndsearch?>a=fake&haha=da&uberParam=yes&q=? HTTP 1.1 OK"); + + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), ""); + UNIT_ASSERT_STRINGS_EQUAL(rd.OrigQueryStringBuf(), ""); + + rd.Scan(); + UNIT_ASSERT(rd.CgiParam.empty()); + } + + Y_UNIT_TEST(CornerCase) { + TServerRequestData rd; + rd.Parse(" /yandsearch?#"); + + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), ""); + UNIT_ASSERT_STRINGS_EQUAL(rd.OrigQueryStringBuf(), ""); + + rd.Scan(); + UNIT_ASSERT(rd.CgiParam.empty()); + } + + Y_UNIT_TEST(AppendQueryString) { + const TString qs("gta=fake&haha=da"); + TServerRequestData rd(qs.c_str()); + + UNIT_ASSERT(rd.CgiParam.Has("gta", "fake")); + UNIT_ASSERT(rd.CgiParam.Has("haha", "da")); + + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), qs); + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), rd.OrigQueryStringBuf()); + + constexpr TStringBuf appendix = "gta=true>a=new"; + rd.AppendQueryString(appendix.data(), appendix.size()); + + UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), qs + '&' + appendix); + UNIT_ASSERT_STRINGS_EQUAL(rd.OrigQueryStringBuf(), qs); + + rd.Scan(); + + UNIT_ASSERT(rd.CgiParam.Has("gta", "true")); + UNIT_ASSERT(rd.CgiParam.Has("gta", "new")); + } + + Y_UNIT_TEST(SetRemoteAddrSimple) { + static const TString TEST = "abacaba.search.yandex.net"; + + TServerRequestData rd; + rd.SetRemoteAddr(TEST); + UNIT_ASSERT_STRINGS_EQUAL(TEST, rd.RemoteAddr()); + } + + Y_UNIT_TEST(SetRemoteAddrRandom) { + for (size_t size = 0; size < 2 * INET6_ADDRSTRLEN; ++size) { + const TString test = NUnitTest::RandomString(size, size); + TServerRequestData rd; + rd.SetRemoteAddr(test); + UNIT_ASSERT_STRINGS_EQUAL(test.substr(0, INET6_ADDRSTRLEN - 1), rd.RemoteAddr()); + } + } + +} // TRequestServerDataTest diff --git a/library/cpp/http/misc/parsed_request.cpp b/library/cpp/http/misc/parsed_request.cpp new file mode 100644 index 0000000000..e332a24e91 --- /dev/null +++ b/library/cpp/http/misc/parsed_request.cpp @@ -0,0 +1,32 @@ +#include "parsed_request.h" + +#include <util/string/strip.h> +#include <util/generic/yexception.h> +#include <util/string/cast.h> + +static inline TStringBuf StripLeft(const TStringBuf& s) noexcept { + const char* b = s.begin(); + const char* e = s.end(); + + StripRangeBegin(b, e); + + return TStringBuf(b, e); +} + +TParsedHttpRequest::TParsedHttpRequest(const TStringBuf& str) { + TStringBuf tmp; + + if (!StripLeft(str).TrySplit(' ', Method, tmp)) { + ythrow yexception() << "bad request(" << ToString(str).Quote() << ")"; + } + + if (!StripLeft(tmp).TrySplit(' ', Request, Proto)) { + ythrow yexception() << "bad request(" << ToString(str).Quote() << ")"; + } + + Proto = StripLeft(Proto); +} + +TParsedHttpLocation::TParsedHttpLocation(const TStringBuf& req) { + req.Split('?', Path, Cgi); +} diff --git a/library/cpp/http/misc/parsed_request.h b/library/cpp/http/misc/parsed_request.h new file mode 100644 index 0000000000..d4df705495 --- /dev/null +++ b/library/cpp/http/misc/parsed_request.h @@ -0,0 +1,26 @@ +#pragma once + +#include <util/generic/strbuf.h> + +struct TParsedHttpRequest { + TParsedHttpRequest(const TStringBuf& str); + + TStringBuf Method; + TStringBuf Request; + TStringBuf Proto; +}; + +struct TParsedHttpLocation { + TParsedHttpLocation(const TStringBuf& req); + + TStringBuf Path; + TStringBuf Cgi; +}; + +struct TParsedHttpFull: public TParsedHttpRequest, public TParsedHttpLocation { + inline TParsedHttpFull(const TStringBuf& line) + : TParsedHttpRequest(line) + , TParsedHttpLocation(Request) + { + } +}; diff --git a/library/cpp/http/misc/parsed_request_ut.cpp b/library/cpp/http/misc/parsed_request_ut.cpp new file mode 100644 index 0000000000..da6d95c6ab --- /dev/null +++ b/library/cpp/http/misc/parsed_request_ut.cpp @@ -0,0 +1,28 @@ +#include "parsed_request.h" + +#include <library/cpp/testing/unittest/registar.h> + +Y_UNIT_TEST_SUITE(THttpParse) { + Y_UNIT_TEST(TestParse) { + TParsedHttpFull h("GET /yandsearch?text=nokia HTTP/1.1"); + + UNIT_ASSERT_EQUAL(h.Method, "GET"); + UNIT_ASSERT_EQUAL(h.Request, "/yandsearch?text=nokia"); + UNIT_ASSERT_EQUAL(h.Proto, "HTTP/1.1"); + + UNIT_ASSERT_EQUAL(h.Path, "/yandsearch"); + UNIT_ASSERT_EQUAL(h.Cgi, "text=nokia"); + } + + Y_UNIT_TEST(TestError) { + bool wasError = false; + + try { + TParsedHttpFull("GET /yandsearch?text=nokiaHTTP/1.1"); + } catch (...) { + wasError = true; + } + + UNIT_ASSERT(wasError); + } +} diff --git a/library/cpp/http/misc/ut/ya.make b/library/cpp/http/misc/ut/ya.make new file mode 100644 index 0000000000..f4bdd35662 --- /dev/null +++ b/library/cpp/http/misc/ut/ya.make @@ -0,0 +1,11 @@ +UNITTEST_FOR(library/cpp/http/misc) + +OWNER(g:util) + +SRCS( + httpdate_ut.cpp + httpreqdata_ut.cpp + parsed_request_ut.cpp +) + +END() diff --git a/library/cpp/http/misc/ya.make b/library/cpp/http/misc/ya.make new file mode 100644 index 0000000000..fceb3cf79c --- /dev/null +++ b/library/cpp/http/misc/ya.make @@ -0,0 +1,24 @@ +LIBRARY() + +OWNER( + g:util + mvel +) + +GENERATE_ENUM_SERIALIZATION(httpcodes.h) + +SRCS( + httpcodes.cpp + httpdate.cpp + httpreqdata.cpp + parsed_request.cpp +) + +PEERDIR( + library/cpp/cgiparam + library/cpp/digest/lower_case +) + +END() + +RECURSE_FOR_TESTS(ut) |