123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- #include "http_common.h"
- #include "location.h"
- #include "http_headers.h"
- #include <util/generic/array_ref.h>
- #include <util/generic/singleton.h>
- #include <util/stream/length.h>
- #include <util/stream/null.h>
- #include <util/stream/str.h>
- #include <util/string/ascii.h>
- 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<TString> urlParts) {
- return urlParts.empty();
- }
- void WriteImpl(const TConstArrayRef<TString> urlParts, IOutputStream& out) {
- NNeh::NHttp::JoinUrlParts(urlParts, out);
- }
- template <typename T>
- size_t GetLength(const T& urlParts) {
- TCountingOutput out(&Cnull);
- WriteImpl(urlParts, out);
- return out.Counter();
- }
- template <typename T>
- void WriteUrl(const T& urlParts, IOutputStream& out) {
- if (!IsEmpty(urlParts)) {
- out << '?';
- WriteImpl(urlParts, out);
- }
- }
- }
- namespace NNeh {
- namespace NHttp {
- size_t GetUrlPartsLength(const TConstArrayRef<TString> 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<TString> 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<TString> 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 <typename T, typename W>
- 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 <typename T>
- 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<TString> 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);
- }
- }
- }
|