aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/http/misc
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/http/misc
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/http/misc')
-rw-r--r--library/cpp/http/misc/http_headers.h72
-rw-r--r--library/cpp/http/misc/httpcodes.cpp141
-rw-r--r--library/cpp/http/misc/httpcodes.h94
-rw-r--r--library/cpp/http/misc/httpdate.cpp83
-rw-r--r--library/cpp/http/misc/httpdate.h21
-rw-r--r--library/cpp/http/misc/httpdate_ut.cpp15
-rw-r--r--library/cpp/http/misc/httpreqdata.cpp196
-rw-r--r--library/cpp/http/misc/httpreqdata.h125
-rw-r--r--library/cpp/http/misc/httpreqdata_ut.cpp154
-rw-r--r--library/cpp/http/misc/parsed_request.cpp32
-rw-r--r--library/cpp/http/misc/parsed_request.h26
-rw-r--r--library/cpp/http/misc/parsed_request_ut.cpp28
-rw-r--r--library/cpp/http/misc/ut/ya.make11
-rw-r--r--library/cpp/http/misc/ya.make24
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?&gta=fake&haha=da HTTP 1.1 OK"));
+
+ // This should work
+ UNIT_ASSERT(rd.Parse(" /yandsearch?&gta=fake&haha=da HTTP 1.1 OK"));
+
+ UNIT_ASSERT_STRINGS_EQUAL(rd.QueryStringBuf(), "&gta=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("&gta=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?&gta=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&gta=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)