http_common.cpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. #include "http_common.h"
  2. #include "location.h"
  3. #include "http_headers.h"
  4. #include <util/generic/array_ref.h>
  5. #include <util/generic/singleton.h>
  6. #include <util/stream/length.h>
  7. #include <util/stream/null.h>
  8. #include <util/stream/str.h>
  9. #include <util/string/ascii.h>
  10. using NNeh::NHttp::ERequestType;
  11. namespace {
  12. bool IsEmpty(const TStringBuf url) {
  13. return url.empty();
  14. }
  15. void WriteImpl(const TStringBuf url, IOutputStream& out) {
  16. out << url;
  17. }
  18. bool IsEmpty(const TConstArrayRef<TString> urlParts) {
  19. return urlParts.empty();
  20. }
  21. void WriteImpl(const TConstArrayRef<TString> urlParts, IOutputStream& out) {
  22. NNeh::NHttp::JoinUrlParts(urlParts, out);
  23. }
  24. template <typename T>
  25. size_t GetLength(const T& urlParts) {
  26. TCountingOutput out(&Cnull);
  27. WriteImpl(urlParts, out);
  28. return out.Counter();
  29. }
  30. template <typename T>
  31. void WriteUrl(const T& urlParts, IOutputStream& out) {
  32. if (!IsEmpty(urlParts)) {
  33. out << '?';
  34. WriteImpl(urlParts, out);
  35. }
  36. }
  37. }
  38. namespace NNeh {
  39. namespace NHttp {
  40. size_t GetUrlPartsLength(const TConstArrayRef<TString> urlParts) {
  41. size_t res = 0;
  42. for (const auto& u : urlParts) {
  43. res += u.length();
  44. }
  45. if (urlParts.size() > 0) {
  46. res += urlParts.size() - 1; //'&' between parts
  47. }
  48. return res;
  49. }
  50. void JoinUrlParts(const TConstArrayRef<TString> urlParts, IOutputStream& out) {
  51. if (urlParts.empty()) {
  52. return;
  53. }
  54. out << urlParts[0];
  55. for (size_t i = 1; i < urlParts.size(); ++i) {
  56. out << '&' << urlParts[i];
  57. }
  58. }
  59. void WriteUrlParts(const TConstArrayRef<TString> urlParts, IOutputStream& out) {
  60. WriteUrl(urlParts, out);
  61. }
  62. }
  63. }
  64. namespace {
  65. const TStringBuf schemeHttps = "https";
  66. const TStringBuf schemeHttp = "http";
  67. const TStringBuf schemeHttp2 = "http2";
  68. const TStringBuf schemePost = "post";
  69. const TStringBuf schemePosts = "posts";
  70. const TStringBuf schemePost2 = "post2";
  71. const TStringBuf schemeFull = "full";
  72. const TStringBuf schemeFulls = "fulls";
  73. const TStringBuf schemeHttpUnix = "http+unix";
  74. const TStringBuf schemePostUnix = "post+unix";
  75. /*
  76. @brief SafeWriteHeaders write headers from hdrs to out with some checks:
  77. - filter out Content-Lenthgh because we'll add it ourselfs later.
  78. @todo ensure headers right formatted (now receive from perl report bad format headers)
  79. */
  80. void SafeWriteHeaders(IOutputStream& out, TStringBuf hdrs) {
  81. NNeh::NHttp::THeaderSplitter splitter(hdrs);
  82. TStringBuf msgHdr;
  83. while (splitter.Next(msgHdr)) {
  84. if (!AsciiHasPrefixIgnoreCase(msgHdr, TStringBuf("Content-Length"))) {
  85. out << msgHdr << TStringBuf("\r\n");
  86. }
  87. }
  88. }
  89. template <typename T, typename W>
  90. TString BuildRequest(const NNeh::TParsedLocation& loc, const T& urlParams, const TStringBuf headers, const W& content, const TStringBuf contentType, ERequestType requestType, NNeh::NHttp::ERequestFlags requestFlags) {
  91. const bool isAbsoluteUri = requestFlags.HasFlags(NNeh::NHttp::ERequestFlag::AbsoluteUri);
  92. const auto contentLength = GetLength(content);
  93. TStringStream out;
  94. 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
  95. + 96); //just some extra space
  96. Y_ASSERT(requestType != ERequestType::Any);
  97. out << requestType;
  98. out << ' ';
  99. if (isAbsoluteUri) {
  100. out << loc.Scheme << TStringBuf("://") << loc.Host;
  101. if (loc.Port) {
  102. out << ':' << loc.Port;
  103. }
  104. }
  105. out << '/' << loc.Service;
  106. WriteUrl(urlParams, out);
  107. out << TStringBuf(" HTTP/1.1\r\n");
  108. NNeh::NHttp::WriteHostHeaderIfNot(out, loc.Host, loc.Port, headers);
  109. SafeWriteHeaders(out, headers);
  110. if (!IsEmpty(content)) {
  111. if (!!contentType && headers.find(TStringBuf("Content-Type:")) == TString::npos) {
  112. out << TStringBuf("Content-Type: ") << contentType << TStringBuf("\r\n");
  113. }
  114. out << TStringBuf("Content-Length: ") << contentLength << TStringBuf("\r\n");
  115. out << TStringBuf("\r\n");
  116. WriteImpl(content, out);
  117. } else {
  118. out << TStringBuf("\r\n");
  119. }
  120. return out.Str();
  121. }
  122. bool NeedGetRequestFor(TStringBuf scheme) {
  123. return scheme == schemeHttp2 || scheme == schemeHttp || scheme == schemeHttps || scheme == schemeHttpUnix;
  124. }
  125. bool NeedPostRequestFor(TStringBuf scheme) {
  126. return scheme == schemePost2 || scheme == schemePost || scheme == schemePosts || scheme == schemePostUnix;
  127. }
  128. inline ERequestType ChooseReqType(ERequestType userReqType, ERequestType defaultReqType) {
  129. Y_ASSERT(defaultReqType != ERequestType::Any);
  130. return userReqType != ERequestType::Any ? userReqType : defaultReqType;
  131. }
  132. }
  133. namespace NNeh {
  134. namespace NHttp {
  135. const TStringBuf DefaultContentType = "application/x-www-form-urlencoded";
  136. template <typename T>
  137. bool MakeFullRequestImpl(TMessage& msg, const TStringBuf proxy, const T& urlParams, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) {
  138. NNeh::TParsedLocation loc(msg.Addr);
  139. if (content.size()) {
  140. //content MUST be placed inside POST requests
  141. if (!IsEmpty(urlParams)) {
  142. if (NeedGetRequestFor(loc.Scheme)) {
  143. msg.Data = BuildRequest(loc, urlParams, headers, content, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags);
  144. } else {
  145. // cannot place in first header line potentially unsafe data from POST message
  146. // (can contain forbidden for url-path characters)
  147. // so support such mutation only for GET requests
  148. return false;
  149. }
  150. } else {
  151. if (NeedGetRequestFor(loc.Scheme) || NeedPostRequestFor(loc.Scheme)) {
  152. msg.Data = BuildRequest(loc, urlParams, headers, content, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags);
  153. } else {
  154. return false;
  155. }
  156. }
  157. } else {
  158. if (NeedGetRequestFor(loc.Scheme)) {
  159. msg.Data = BuildRequest(loc, urlParams, headers, "", "", ChooseReqType(reqType, ERequestType::Get), reqFlags);
  160. } else if (NeedPostRequestFor(loc.Scheme)) {
  161. msg.Data = BuildRequest(loc, TString(), headers, urlParams, contentType, ChooseReqType(reqType, ERequestType::Post), reqFlags);
  162. } else {
  163. return false;
  164. }
  165. }
  166. if (proxy.IsInited()) {
  167. loc = NNeh::TParsedLocation(proxy);
  168. msg.Addr = proxy;
  169. }
  170. TString schemePostfix = "";
  171. if (loc.Scheme.EndsWith("+unix")) {
  172. schemePostfix = "+unix";
  173. }
  174. // ugly but still... https2 will break it :(
  175. if ('s' == loc.Scheme[loc.Scheme.size() - 1]) {
  176. msg.Addr.replace(0, loc.Scheme.size(), schemeFulls + schemePostfix);
  177. } else {
  178. msg.Addr.replace(0, loc.Scheme.size(), schemeFull + schemePostfix);
  179. }
  180. return true;
  181. }
  182. bool MakeFullRequest(TMessage& msg, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) {
  183. return MakeFullRequestImpl(msg, {}, msg.Data, headers, content, contentType, reqType, reqFlags);
  184. }
  185. bool MakeFullRequest(TMessage& msg, const TConstArrayRef<TString> urlParts, const TStringBuf headers, const TStringBuf content, const TStringBuf contentType, ERequestType reqType, ERequestFlags reqFlags) {
  186. return MakeFullRequestImpl(msg, {}, urlParts, headers, content, contentType, reqType, reqFlags);
  187. }
  188. bool MakeFullProxyRequest(TMessage& msg, TStringBuf proxyAddr, TStringBuf headers, TStringBuf content, TStringBuf contentType, ERequestType reqType, ERequestFlags flags) {
  189. return MakeFullRequestImpl(msg, proxyAddr, msg.Data, headers, content, contentType, reqType, flags | ERequestFlag::AbsoluteUri);
  190. }
  191. bool IsHttpScheme(TStringBuf scheme) {
  192. return NeedGetRequestFor(scheme) || NeedPostRequestFor(scheme);
  193. }
  194. }
  195. }