#include "http_common.h" #include "location.h" #include "http_headers.h" #include #include #include #include #include #include using NNeh::NHttp::ERequestType; namespace { bool IsEmpty(const TStringBuf url) { return url.empty(); } void WriteImpl(const TStringBuf url, IOutputStream& out) { out << url; } bool IsEmpty(const TConstArrayRef urlParts) { return urlParts.empty(); } void WriteImpl(const TConstArrayRef urlParts, IOutputStream& out) { NNeh::NHttp::JoinUrlParts(urlParts, out); } template size_t GetLength(const T& urlParts) { TCountingOutput out(&Cnull); WriteImpl(urlParts, out); return out.Counter(); } template void WriteUrl(const T& urlParts, IOutputStream& out) { if (!IsEmpty(urlParts)) { out << '?'; WriteImpl(urlParts, out); } } } namespace NNeh { namespace NHttp { size_t GetUrlPartsLength(const TConstArrayRef urlParts) { size_t res = 0; for (const auto& u : urlParts) { res += u.length(); } if (urlParts.size() > 0) { res += urlParts.size() - 1; //'&' between parts } return res; } void JoinUrlParts(const TConstArrayRef urlParts, IOutputStream& out) { if (urlParts.empty()) { return; } out << urlParts[0]; for (size_t i = 1; i < urlParts.size(); ++i) { out << '&' << urlParts[i]; } } void WriteUrlParts(const TConstArrayRef urlParts, IOutputStream& out) { WriteUrl(urlParts, out); } } } namespace { const TStringBuf schemeHttps = "https"; const TStringBuf schemeHttp = "http"; const TStringBuf schemeHttp2 = "http2"; const TStringBuf schemePost = "post"; const TStringBuf schemePosts = "posts"; const TStringBuf schemePost2 = "post2"; const TStringBuf schemeFull = "full"; const TStringBuf schemeFulls = "fulls"; const TStringBuf schemeHttpUnix = "http+unix"; const TStringBuf schemePostUnix = "post+unix"; /* @brief SafeWriteHeaders write headers from hdrs to out with some checks: - filter out Content-Lenthgh because we'll add it ourselfs later. @todo ensure headers right formatted (now receive from perl report bad format headers) */ void SafeWriteHeaders(IOutputStream& out, TStringBuf hdrs) { NNeh::NHttp::THeaderSplitter splitter(hdrs); TStringBuf msgHdr; while (splitter.Next(msgHdr)) { if (!AsciiHasPrefixIgnoreCase(msgHdr, TStringBuf("Content-Length"))) { out << msgHdr << TStringBuf("\r\n"); } } } template TString BuildRequest(const NNeh::TParsedLocation& loc, const T& urlParams, const TStringBuf headers, const W& content, const TStringBuf contentType, ERequestType requestType, NNeh::NHttp::ERequestFlags requestFlags) { const bool isAbsoluteUri = requestFlags.HasFlags(NNeh::NHttp::ERequestFlag::AbsoluteUri); const auto contentLength = GetLength(content); TStringStream out; out.Reserve(loc.Service.length() + loc.Host.length() + GetLength(urlParams) + headers.length() + contentType.length() + contentLength + (isAbsoluteUri ? (loc.Host.length() + 13) : 0) // 13 - is a max port number length + scheme length + 96); //just some extra space Y_ASSERT(requestType != ERequestType::Any); out << requestType; out << ' '; if (isAbsoluteUri) { out << loc.Scheme << TStringBuf("://") << loc.Host; if (loc.Port) { out << ':' << loc.Port; } } out << '/' << loc.Service; WriteUrl(urlParams, out); out << TStringBuf(" HTTP/1.1\r\n"); NNeh::NHttp::WriteHostHeaderIfNot(out, loc.Host, loc.Port, headers); SafeWriteHeaders(out, headers); if (!IsEmpty(content)) { if (!!contentType && headers.find(TStringBuf("Content-Type:")) == TString::npos) { out << TStringBuf("Content-Type: ") << contentType << TStringBuf("\r\n"); } out << TStringBuf("Content-Length: ") << contentLength << TStringBuf("\r\n"); out << TStringBuf("\r\n"); WriteImpl(content, out); } else { out << TStringBuf("\r\n"); } return out.Str(); } bool NeedGetRequestFor(TStringBuf scheme) { return scheme == schemeHttp2 || scheme == schemeHttp || scheme == schemeHttps || scheme == schemeHttpUnix; } bool NeedPostRequestFor(TStringBuf scheme) { return scheme == schemePost2 || scheme == schemePost || scheme == schemePosts || scheme == schemePostUnix; } inline ERequestType ChooseReqType(ERequestType userReqType, ERequestType defaultReqType) { Y_ASSERT(defaultReqType != ERequestType::Any); return userReqType != ERequestType::Any ? userReqType : defaultReqType; } } namespace NNeh { namespace NHttp { const TStringBuf DefaultContentType = "application/x-www-form-urlencoded"; template bool MakeFullRequestImpl(TMessage& msg, const TStringBuf proxy, const T& urlParams, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) { NNeh::TParsedLocation loc(msg.Addr); if (content.size()) { //content MUST be placed inside POST requests if (!IsEmpty(urlParams)) { if (NeedGetRequestFor(loc.Scheme)) { msg.Data = BuildRequest(loc, urlParams, headers, content, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags); } else { // cannot place in first header line potentially unsafe data from POST message // (can contain forbidden for url-path characters) // so support such mutation only for GET requests return false; } } else { if (NeedGetRequestFor(loc.Scheme) || NeedPostRequestFor(loc.Scheme)) { msg.Data = BuildRequest(loc, urlParams, headers, content, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags); } else { return false; } } } else { if (NeedGetRequestFor(loc.Scheme)) { msg.Data = BuildRequest(loc, urlParams, headers, "", "", ChooseReqType(reqType, ERequestType::Get), reqFlags); } else if (NeedPostRequestFor(loc.Scheme)) { msg.Data = BuildRequest(loc, TString(), headers, urlParams, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags); } else { return false; } } if (proxy.IsInited()) { loc = NNeh::TParsedLocation(proxy); msg.Addr = proxy; } TString schemePostfix = ""; if (loc.Scheme.EndsWith("+unix")) { schemePostfix = "+unix"; } // ugly but still... https2 will break it :( if ('s' == loc.Scheme[loc.Scheme.size() - 1]) { msg.Addr.replace(0, loc.Scheme.size(), schemeFulls + schemePostfix); } else { msg.Addr.replace(0, loc.Scheme.size(), schemeFull + schemePostfix); } return true; } bool MakeFullRequest(TMessage& msg, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) { return MakeFullRequestImpl(msg, {}, msg.Data, headers, content, contentType, reqType, reqFlags); } bool MakeFullRequest(TMessage& msg, const TConstArrayRef urlParts, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) { return MakeFullRequestImpl(msg, {}, urlParts, headers, content, contentType, reqType, reqFlags); } bool MakeFullProxyRequest(TMessage& msg, TStringBuf proxyAddr, TStringBuf headers, TStringBuf content, TStringBuf contentType, ERequestType reqType, ERequestFlags flags) { return MakeFullRequestImpl(msg, proxyAddr, msg.Data, headers, content, contentType, reqType, flags | ERequestFlag::AbsoluteUri); } bool IsHttpScheme(TStringBuf scheme) { return NeedGetRequestFor(scheme) || NeedPostRequestFor(scheme); } } }