Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / server / http-protocol.cpp
blob130fa83df710de8145d9664bb28a2136c6d511fa
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/server/http-protocol.h"
18 #include <map>
19 #include <string>
21 #include <folly/Conv.h>
22 #include <folly/portability/SysTime.h>
24 #include "hphp/util/arch.h"
25 #include "hphp/util/logger.h"
26 #include "hphp/util/stack-trace.h"
27 #include "hphp/util/text-util.h"
29 #include "hphp/runtime/base/array-init.h"
30 #include "hphp/runtime/base/builtin-functions.h"
31 #include "hphp/runtime/base/http-client.h"
32 #include "hphp/runtime/base/program-functions.h"
33 #include "hphp/runtime/base/request-injection-data.h"
34 #include "hphp/runtime/base/runtime-option.h"
35 #include "hphp/runtime/base/string-util.h"
36 #include "hphp/runtime/base/zend-string.h"
37 #include "hphp/runtime/base/zend-url.h"
38 #include "hphp/runtime/ext/string/ext_string.h"
39 #include "hphp/runtime/server/cli-server.h"
40 #include "hphp/runtime/server/replay-transport.h"
41 #include "hphp/runtime/server/request-uri.h"
42 #include "hphp/runtime/server/source-root-info.h"
43 #include "hphp/runtime/server/transport.h"
44 #include "hphp/runtime/server/upload.h"
45 #include "hphp/runtime/server/virtual-host.h"
46 #include "hphp/runtime/vm/globals-array.h"
48 #define DEFAULT_POST_CONTENT_TYPE "application/x-www-form-urlencoded"
50 using std::map;
51 using std::string;
53 namespace HPHP {
54 ///////////////////////////////////////////////////////////////////////////////
55 // helper functions
57 static bool read_all_post_data(Transport *transport,
58 const void *&data, size_t &size) {
59 if (transport->hasMorePostData()) {
60 data = buffer_duplicate(data, size);
61 do {
62 size_t delta = 0;
63 const void *extra = transport->getMorePostData(delta);
64 if (size + delta < VirtualHost::GetMaxPostSize()) {
65 data = buffer_append(data, size, extra, delta);
66 size += delta;
68 } while (transport->hasMorePostData());
69 return true;
71 return false;
74 static void CopyParams(Array& dest, Array& src) {
75 for (ArrayIter iter(src); iter; ++iter) {
76 dest.set(iter.first(), iter.second());
80 ///////////////////////////////////////////////////////////////////////////////
82 const VirtualHost *HttpProtocol::GetVirtualHost(Transport *transport) {
83 if (!RuntimeOption::VirtualHosts.empty()) {
84 std::string host = transport->getHeader("Host");
85 if (auto vh = VirtualHost::Resolve(host)) {
86 VirtualHost::SetCurrent(vh);
87 return vh;
90 VirtualHost::SetCurrent(nullptr);
91 return VirtualHost::GetCurrent();
94 const StaticString
95 s_REQUEST_START_TIME("REQUEST_START_TIME"),
96 s_HPHP("HPHP"),
97 s_HHVM("HHVM"),
98 s_HHVM_JIT("HHVM_JIT"),
99 s_HHVM_ARCH("HHVM_ARCH"),
100 s_HPHP_SERVER("HPHP_SERVER"),
101 s_HPHP_HOTPROFILER("HPHP_HOTPROFILER"),
102 s_HTTP_HOST("HTTP_HOST"),
103 s_CONTENT_TYPE("CONTENT_TYPE"),
104 s_CONTENT_LENGTH("CONTENT_LENGTH"),
105 s_PHP_AUTH_USER("PHP_AUTH_USER"),
106 s_PHP_AUTH_PW("PHP_AUTH_PW"),
107 s_PHP_AUTH_DIGEST("PHP_AUTH_DIGEST"),
108 s_REQUEST_URI("REQUEST_URI"),
109 s_SCRIPT_URL("SCRIPT_URL"),
110 s_SCRIPT_URI("SCRIPT_URI"),
111 s_SCRIPT_NAME("SCRIPT_NAME"),
112 s_PHP_SELF("PHP_SELF"),
113 s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
114 s_PATH_TRANSLATED("PATH_TRANSLATED"),
115 s_PATH_INFO("PATH_INFO"),
116 s_argc("argc"),
117 s_argv("argv"),
118 s__SERVER("_SERVER"),
119 s__GET("_GET"),
120 s__POST("_POST"),
121 s__REQUEST("_REQUEST"),
122 s__SESSION("_SESSION"),
123 s__ENV("_ENV"),
124 s__COOKIE("_COOKIE"),
125 s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
126 s__FILES("_FILES"),
127 s_GATEWAY_INTERFACE("GATEWAY_INTERFACE"),
128 s_CGI_1_1("CGI/1.1"),
129 s_SERVER_ADDR("SERVER_ADDR"),
130 s_SERVER_NAME("SERVER_NAME"),
131 s_SERVER_PORT("SERVER_PORT"),
132 s_SERVER_SOFTWARE("SERVER_SOFTWARE"),
133 s_SERVER_PROTOCOL("SERVER_PROTOCOL"),
134 s_SERVER_ADMIN("SERVER_ADMIN"),
135 s_SERVER_SIGNATURE("SERVER_SIGNATURE"),
136 s_REQUEST_METHOD("REQUEST_METHOD"),
137 s_GET("GET"),
138 s_HEAD("HEAD"),
139 s_POST("POST"),
140 s_HTTPS("HTTPS"),
141 s_on("on"),
142 s_REQUEST_TIME("REQUEST_TIME"),
143 s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
144 s_QUERY_STRING("QUERY_STRING"),
145 s_REMOTE_ADDR("REMOTE_ADDR"),
146 s_REMOTE_HOST("REMOTE_HOST"),
147 s_REMOTE_PORT("REMOTE_PORT"),
148 s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
149 s_THREAD_TYPE("THREAD_TYPE"),
150 s_dash("-"),
151 s_underscore("_"),
152 s_HTTP_("HTTP_"),
153 s_forwardslash("/");
155 static auto const s_arraysToClear = {
156 s__SERVER,
157 s__GET,
158 s__POST,
159 s__FILES,
160 s__REQUEST,
161 s__ENV,
162 s__COOKIE,
165 static auto const s_arraysToUnset = {
166 s__SESSION,
169 static void PrepareEnv(Array& env, Transport *transport) {
170 // $_ENV
171 process_env_variables(env);
172 env.set(s_HPHP, 1);
173 env.set(s_HHVM, 1);
174 if (RuntimeOption::EvalJit) {
175 env.set(s_HHVM_JIT, 1);
177 switch (arch()) {
178 case Arch::X64:
179 env.set(s_HHVM_ARCH, "x64");
180 break;
181 case Arch::ARM:
182 env.set(s_HHVM_ARCH, "arm");
183 break;
184 case Arch::PPC64:
185 env.set(s_HHVM_ARCH, "ppc64");
186 break;
189 bool isServer =
190 RuntimeOption::ServerExecutionMode() && !is_cli_mode();
191 if (isServer) {
192 env.set(s_HPHP_SERVER, 1);
193 env.set(s_HPHP_HOTPROFILER, 1);
196 // Do this last so it can overwrite all the previous settings
197 HeaderMap transportParams;
198 transport->getTransportParams(transportParams);
199 for (auto const& header : transportParams) {
200 String key(header.first);
201 String value(header.second.back());
202 g_context->setenv(key, value);
203 env.set(key, value);
207 static void StartRequest(Array& server) {
208 server.set(s_REQUEST_START_TIME, time(nullptr));
209 time_t now;
210 struct timeval tp = {0};
211 double now_double;
212 if (!gettimeofday(&tp, nullptr)) {
213 now_double = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
214 now = tp.tv_sec;
215 } else {
216 now = time(nullptr);
217 now_double = (double)now;
219 server.set(s_REQUEST_TIME, now);
220 server.set(s_REQUEST_TIME_FLOAT, now_double);
224 * PHP has "EGPCS" processing order of these global variables, and this
225 * order is important in preparing $_REQUEST that needs to know which to
226 * overwrite what when name happens to be the same.
228 void HttpProtocol::PrepareSystemVariables(Transport *transport,
229 const RequestURI &r,
230 const SourceRootInfo &sri) {
231 SuppressHackArrCompatNotices suppress;
233 auto const vhost = VirtualHost::GetCurrent();
234 auto const g = get_global_variables()->asArrayData();
235 Variant emptyArr(staticEmptyArray());
236 for (auto& key : s_arraysToClear) {
237 g->remove(key.get(), false);
238 g->set(key.get(), emptyArr, false);
240 for (auto& key : s_arraysToUnset) {
241 g->remove(key.get(), false);
244 // according to doc if content type is multipart/form-data
245 // $HTTP_RAW_POST_DATA should always not available
246 bool shouldSetHttpRawPostData = false;
247 if (RuntimeOption::AlwaysPopulateRawPostData) {
248 std::string dummy;
249 if (!IsRfc1867(transport->getHeader("Content-Type"), dummy)) {
250 shouldSetHttpRawPostData = true;
254 if (shouldSetHttpRawPostData) {
255 g->set(s_HTTP_RAW_POST_DATA, empty_string_variant_ref, false);
258 #define X(name) \
259 Array name##arr(Array::Create()); \
260 SCOPE_EXIT { g->set(s__##name, name##arr, false); };
262 X(ENV)
263 X(GET)
264 X(POST)
265 X(COOKIE)
266 X(FILES)
267 X(SERVER)
268 X(REQUEST)
270 #undef X
272 Variant HTTP_RAW_POST_DATA;
273 SCOPE_EXIT {
274 if (shouldSetHttpRawPostData) {
275 g->set(s_HTTP_RAW_POST_DATA.get(), HTTP_RAW_POST_DATA, false);
279 auto variablesOrder = ThreadInfo::s_threadInfo.getNoCheck()
280 ->m_reqInjectionData.getVariablesOrder();
282 auto requestOrder = ThreadInfo::s_threadInfo.getNoCheck()
283 ->m_reqInjectionData.getRequestOrder();
285 if (requestOrder.empty()) {
286 requestOrder = variablesOrder;
289 bool postPopulated = false;
291 for (char& c : variablesOrder) {
292 switch(c) {
293 case 'e':
294 case 'E':
295 PrepareEnv(ENVarr, transport);
296 break;
297 case 'g':
298 case 'G':
299 if (!r.queryString().empty()) {
300 PrepareGetVariable(GETarr, r);
302 break;
303 case 'p':
304 case 'P':
305 postPopulated = true;
306 PreparePostVariables(POSTarr, HTTP_RAW_POST_DATA,
307 FILESarr, transport, r);
308 break;
309 case 'c':
310 case 'C':
311 PrepareCookieVariable(COOKIEarr, transport);
312 break;
313 case 's':
314 case 'S':
315 StartRequest(SERVERarr);
316 PrepareServerVariable(SERVERarr,
317 transport,
319 sri,
320 vhost);
321 break;
325 if (!postPopulated && shouldSetHttpRawPostData) {
326 // Always try to populate $HTTP_RAW_POST_DATA if not populated
327 Array dummyPost(Array::Create());
328 Array dummyFiles(Array::Create());
329 PreparePostVariables(dummyPost, HTTP_RAW_POST_DATA,
330 dummyFiles, transport, r);
333 PrepareRequestVariables(REQUESTarr,
334 GETarr,
335 POSTarr,
336 COOKIEarr,
337 requestOrder);
340 void HttpProtocol::PrepareRequestVariables(Array& request,
341 Array& get,
342 Array& post,
343 Array& cookie,
344 const std::string& requestOrder) {
345 for (const char& c : requestOrder) {
346 switch(c) {
347 case 'g':
348 case 'G':
349 CopyParams(request, get);
350 break;
351 case 'p':
352 case 'P':
353 CopyParams(request, post);
354 break;
355 case 'c':
356 case 'C':
357 CopyParams(request, cookie);
358 break;
364 void HttpProtocol::PrepareGetVariable(Array& get,
365 const RequestURI &r) {
366 DecodeParameters(get,
367 r.queryString().data(),
368 r.queryString().size());
371 void HttpProtocol::PreparePostVariables(Array& post,
372 Variant& raw_post,
373 Array& files,
374 Transport *transport,
375 const RequestURI& r) {
376 if (transport->getMethod() != Transport::Method::POST) {
377 return;
380 std::string contentType = transport->getHeader("Content-Type");
381 std::string contentLength = transport->getHeader("Content-Length");
383 bool needDelete = false;
384 size_t size = 0;
385 const void *data = transport->getPostData(size);
386 if (data && size) {
387 std::string boundary;
388 auto content_length = strtoll(contentLength.c_str(), nullptr, 10);
389 bool rfc1867Post = IsRfc1867(contentType, boundary);
390 std::string files_str;
391 if (rfc1867Post) {
392 if (content_length < 0 ||
393 content_length > VirtualHost::GetMaxPostSize()) {
394 // $_POST and $_FILES are empty
395 Logger::Warning("POST Content-Length of %lld bytes exceeds "
396 "the limit of %" PRId64 " bytes",
397 content_length, VirtualHost::GetMaxPostSize());
398 while (transport->hasMorePostData()) {
399 size_t delta = 0;
400 transport->getMorePostData(delta);
402 data = nullptr;
403 size = 0;
404 } else {
405 // content_length is a reasonable nonnegative size.
406 bool invalidate = false;
407 if (transport->hasMorePostData()) {
408 // Calls to getMorePostData may invalidate data, so make a copy
409 // iff we're trying to coalesce the entire POST body. Otherwise,
410 // data may be invalid when DecodeRfc1867 returns. See
411 // upload.cpp:read_post.
412 if (RuntimeOption::AlwaysPopulateRawPostData) {
413 needDelete = true;
414 data = buffer_duplicate(data, size);
415 } else {
416 invalidate = true;
419 DecodeRfc1867(transport,
420 post,
421 files,
422 content_length,
423 data,
424 size,
425 boundary);
426 if (invalidate) {
427 data = nullptr;
428 size = 0;
431 assertx(!transport->getFiles(files_str));
432 } else {
433 needDelete = read_all_post_data(transport, data, size);
435 bool decodeData = strncasecmp(contentType.c_str(),
436 DEFAULT_POST_CONTENT_TYPE,
437 sizeof(DEFAULT_POST_CONTENT_TYPE)-1) == 0;
439 if (!decodeData) {
440 auto vhost = VirtualHost::GetCurrent();
441 if (vhost && vhost->alwaysDecodePostData(r.originalURL())) {
442 decodeData = true;
446 if (decodeData) {
447 DecodeParameters(post, (const char*)data, size, true);
450 bool ret = transport->getFiles(files_str);
451 if (ret) {
452 files = unserialize_from_string(
453 files_str,
454 VariableUnserializer::Type::Serialize
459 if (!data) {
460 return;
462 if (size > StringData::MaxSize) {
463 // Can't store it anywhere
464 if (needDelete) {
465 free((void*) data);
467 } else {
468 auto string_data = needDelete ?
469 String((char*)data, size, AttachString) :
470 String((char*)data, size, CopyString);
471 g_context->setRawPostData(string_data);
472 if (RuntimeOption::AlwaysPopulateRawPostData || ! needDelete) {
473 // For literal we disregard RuntimeOption::AlwaysPopulateRawPostData
474 raw_post = string_data;
480 bool HttpProtocol::PrepareCookieVariable(Array& cookie,
481 Transport *transport) {
483 string cookie_data = transport->getHeader("Cookie");
484 if (!cookie_data.empty()) {
485 StringBuffer sb;
486 sb.append(cookie_data);
487 DecodeCookies(cookie, (char*)sb.data());
488 return true;
489 } else {
490 return false;
494 static void CopyHeaderVariables(Array& server,
495 const HeaderMap& headers) {
496 static std::atomic<int> badRequests(-1);
498 std::vector<std::string> badHeaders;
499 for (auto const& header : headers) {
500 auto const& key = header.first;
501 auto const& values = header.second;
502 auto normalizedKey = s_HTTP_ +
503 string_replace(HHVM_FN(strtoupper)(key), s_dash,
504 s_underscore);
506 // Detect suspicious headers. We are about to modify header names for
507 // the SERVER variable. This means that it is possible to deliberately
508 // cause a header collision, which an attacker could use to sneak a
509 // header past a proxy that would either overwrite or filter it
510 // otherwise. Client code should use apache_request_headers() to
511 // retrieve the original headers if they are security-critical.
512 if (RuntimeOption::LogHeaderMangle != 0 && server.exists(normalizedKey)) {
513 badHeaders.push_back(key);
516 if (!values.empty()) {
517 // When a header has multiple values, we always take the last one.
518 server.set(normalizedKey, String(values.back()));
522 if (!badHeaders.empty()) {
523 auto reqId = badRequests.fetch_add(1, std::memory_order_acq_rel) + 1;
524 if (!(reqId % RuntimeOption::LogHeaderMangle)) {
525 std::string badNames = folly::join(", ", badHeaders);
526 std::string allHeaders;
528 const char* separator = "";
529 for (auto const& header : headers) {
530 for (auto const& value : header.second) {
531 folly::toAppend(separator, header.first, ": ", value,
532 &allHeaders);
533 separator = "\n";
537 Logger::Warning(
538 "HeaderMangle warning: "
539 "The header(s) [%s] overwrote other headers which mapped to the same "
540 "key. This happens because PHP normalises - to _, ie AN_EXAMPLE "
541 "and AN-EXAMPLE are equivalent. You should treat this as "
542 "malicious. All headers from this request:\n%s",
543 badNames.c_str(), allHeaders.c_str());
548 static void CopyTransportParams(Array& server, Transport* transport) {
549 HeaderMap transportParams;
550 // Get additional server params from the transport if it has any. In the case
551 // of fastcgi this is basically a full header list from apache/nginx.
552 transport->getTransportParams(transportParams);
553 for (auto const& header : transportParams) {
554 // These overwrite anything already set in the $_SERVER
555 // When a header has multiple values, we always take the last one.
556 server.set(String(header.first), String(header.second.back()));
560 static void CopyServerInfo(Array& server,
561 Transport *transport,
562 const VirtualHost *vhost) {
564 string hostHeader = transport->getHeader("Host");
565 String hostName(vhost->serverName(hostHeader));
566 String serverNameHeader(transport->getServerName());
567 if (hostHeader.empty()) {
568 server.set(s_HTTP_HOST, hostName);
569 StackTraceNoHeap::AddExtraLogging("Server", hostName.data());
570 } else {
571 // reset the HTTP_HOST header from apache.
572 server.set(s_HTTP_HOST, hostHeader);
573 StackTraceNoHeap::AddExtraLogging("Server", hostHeader.c_str());
576 // Use the header from the transport if it is available
577 if (!serverNameHeader.empty()) {
578 hostName = serverNameHeader;
579 } else if (hostName.empty() || RuntimeOption::ForceServerNameToHeader) {
580 hostName = hostHeader;
583 // _SERVER['SERVER_NAME'] shouldn't contain the port number
584 int colonPos = hostName.find(':');
585 if (colonPos != String::npos) {
586 hostName = hostName.substr(0, colonPos);
589 StackTraceNoHeap::AddExtraLogging("Server_SERVER_NAME", hostName.data());
591 server.set(s_GATEWAY_INTERFACE, s_CGI_1_1);
592 server.set(s_SERVER_ADDR, transport->getServerAddr());
593 server.set(s_SERVER_NAME, hostName);
594 server.set(s_SERVER_PORT, transport->getServerPort());
595 server.set(s_SERVER_SOFTWARE, transport->getServerSoftware());
596 server.set(s_SERVER_PROTOCOL, "HTTP/" + transport->getHTTPVersion());
597 server.set(s_SERVER_ADMIN, empty_string_variant_ref);
598 server.set(s_SERVER_SIGNATURE, empty_string_variant_ref);
601 static void CopyRemoteInfo(Array& server, Transport *transport) {
602 String remoteAddr(transport->getRemoteAddr(), CopyString);
603 String remoteHost(transport->getRemoteHost(), CopyString);
604 if (remoteAddr.empty()) {
605 remoteAddr = remoteHost;
607 // Always set remoteAddr, even if it is empty
608 server.set(s_REMOTE_ADDR, remoteAddr);
609 if (!remoteHost.empty()) {
610 server.set(s_REMOTE_HOST, remoteHost);
612 server.set(s_REMOTE_PORT, transport->getRemotePort());
615 static void CopyAuthInfo(Array& server, Transport *transport) {
616 // APE processes Authorization: Basic into PHP_AUTH_USER and PHP_AUTH_PW
617 string authorization = transport->getHeader("Authorization");
618 if (!authorization.empty()) {
619 if (strncmp(authorization.c_str(), "Basic ", 6) == 0) {
620 // it's safe to pass this as a string literal since authorization
621 // outlives decodedAuth; this saves us a superfluous copy.
622 String decodedAuth =
623 StringUtil::Base64Decode(String(authorization.c_str() + 6));
624 int colonPos = decodedAuth.find(':');
625 if (colonPos != String::npos) {
626 server.set(s_PHP_AUTH_USER, decodedAuth.substr(0, colonPos));
627 server.set(s_PHP_AUTH_PW, decodedAuth.substr(colonPos + 1));
629 } else if (strncmp(authorization.c_str(), "Digest ", 7) == 0) {
630 server.set(s_PHP_AUTH_DIGEST, String(authorization.c_str() + 7));
635 static void CopyPathInfo(Array& server,
636 Transport *transport,
637 const RequestURI& r,
638 const VirtualHost *vhost) {
639 server.set(s_REQUEST_URI, String(transport->getUrl(), CopyString));
640 server.set(s_SCRIPT_URL, r.originalURL());
641 String prefix(transport->isSSL() ? "https://" : "http://");
643 // Need to append port
644 assertx(server.exists(s_SERVER_PORT));
645 std::string serverPort = "80";
646 if (server.exists(s_SERVER_PORT)) {
647 Variant port = server[s_SERVER_PORT];
648 always_assert(port.isInteger() || port.isString());
649 if (port.isInteger()) {
650 serverPort = folly::to<std::string>(port.toInt32());
651 } else {
652 serverPort = port.toString().data();
656 String port_suffix("");
657 if (!transport->isSSL() && serverPort != "80") {
658 port_suffix = folly::format(":{}", serverPort).str();
661 string hostHeader;
662 if (server.exists(s_HTTP_HOST)) {
663 hostHeader = server[s_HTTP_HOST].toCStrRef().data();
665 String hostName;
666 if (server.exists(s_SERVER_NAME)) {
667 assertx(server[s_SERVER_NAME].isString());
668 hostName = server[s_SERVER_NAME].toCStrRef();
670 server.set(s_SCRIPT_URI,
671 String(prefix + (hostHeader.empty() ? hostName + port_suffix :
672 String(hostHeader)) + r.originalURL()));
674 if (r.rewritten()) {
675 // when URL is rewritten, PHP decided to put original URL as SCRIPT_NAME
676 String name = r.originalURL();
677 if (!r.pathInfo().empty()) {
678 int pos = name.find(r.pathInfo());
679 if (pos >= 0) {
680 name = name.substr(0, pos);
683 if (r.defaultDoc()) {
684 if (!name.empty() && name[name.length() - 1] != '/') {
685 name += "/";
687 name += String(RuntimeOption::DefaultDocument);
689 server.set(s_SCRIPT_NAME, name);
690 } else {
691 server.set(s_SCRIPT_NAME, r.resolvedURL());
694 if (r.rewritten()) {
695 server.set(s_PHP_SELF, r.originalURL());
696 } else {
697 server.set(s_PHP_SELF, r.resolvedURL() + r.origPathInfo());
700 String documentRoot = transport->getDocumentRoot();
701 if (documentRoot.empty()) {
702 // Right now this is just RuntimeOption::SourceRoot but mwilliams wants to
703 // fix it so it is settable, so I'll leave this for now
704 documentRoot = vhost->getDocumentRoot();
706 if (documentRoot != s_forwardslash &&
707 documentRoot[documentRoot.length() - 1] == '/') {
708 documentRoot = documentRoot.substr(0, documentRoot.length() - 1);
710 server.set(s_DOCUMENT_ROOT, documentRoot);
711 server.set(s_SCRIPT_FILENAME, r.absolutePath());
713 if (r.pathInfo().empty()) {
714 server.set(s_PATH_TRANSLATED, r.absolutePath());
715 } else {
716 assertx(server.exists(s_DOCUMENT_ROOT));
717 assertx(server[s_DOCUMENT_ROOT].isString());
718 // reset path_translated back to the transport if it has it.
719 auto const& pathTranslated = transport->getPathTranslated();
720 if (!pathTranslated.empty()) {
721 if (documentRoot == s_forwardslash) {
722 // path outside document root or / is document root
723 server.set(s_PATH_TRANSLATED, String(pathTranslated));
724 } else {
725 server.set(s_PATH_TRANSLATED,
726 String(server[s_DOCUMENT_ROOT].toCStrRef() +
727 s_forwardslash + pathTranslated));
729 } else {
730 server.set(s_PATH_TRANSLATED,
731 String(server[s_DOCUMENT_ROOT].toCStrRef() +
732 server[s_SCRIPT_NAME].toCStrRef() +
733 r.pathInfo().data()));
735 server.set(s_PATH_INFO, r.pathInfo());
738 switch (transport->getMethod()) {
739 case Transport::Method::GET: server.set(s_REQUEST_METHOD, s_GET); break;
740 case Transport::Method::HEAD: server.set(s_REQUEST_METHOD, s_HEAD); break;
741 case Transport::Method::POST:
742 if (transport->getExtendedMethod() == nullptr) {
743 server.set(s_REQUEST_METHOD, s_POST);
744 } else {
745 server.set(s_REQUEST_METHOD, transport->getExtendedMethod());
747 break;
748 default:
749 server.set(s_REQUEST_METHOD, empty_string_variant_ref); break;
751 server.set(s_HTTPS, transport->isSSL() ? Variant(s_on) :
752 empty_string_variant_ref);
753 server.set(s_QUERY_STRING, r.queryString());
755 server.set(s_argv, make_packed_array(r.queryString()));
756 server.set(s_argc, 1);
759 void HttpProtocol::PrepareServerVariable(Array& server,
760 Transport *transport,
761 const RequestURI &r,
762 const SourceRootInfo &sri,
763 const VirtualHost *vhost) {
764 // $_SERVER
766 string contentType = transport->getHeader("Content-Type");
767 string contentLength = transport->getHeader("Content-Length");
769 // HTTP_ headers -- we don't exclude headers we handle elsewhere (e.g.,
770 // Content-Type, Authorization), since the CGI "spec" merely says the server
771 // "may" exclude them; this is not what APE does, but it's harmless.
772 HeaderMap headers;
773 transport->getHeaders(headers);
774 // Do this first so other methods can overwrite them
775 CopyHeaderVariables(server, headers);
776 CopyServerInfo(server, transport, vhost);
777 // Do this last so it can overwrite all the previous settings
778 CopyTransportParams(server, transport);
779 CopyRemoteInfo(server, transport);
780 CopyAuthInfo(server, transport);
781 CopyPathInfo(server, transport, r, vhost);
783 // APE sets CONTENT_TYPE and CONTENT_LENGTH without HTTP_
784 if (!contentType.empty()) {
785 server.set(s_CONTENT_TYPE, String(contentType));
787 if (!contentLength.empty()) {
788 server.set(s_CONTENT_LENGTH, String(contentLength));
791 for (auto& kv : RuntimeOption::ServerVariables) {
792 server.set(String(kv.first), String(kv.second));
794 for (auto& kv : vhost->getServerVars()) {
795 server.set(String(kv.first), String(kv.second));
797 server = sri.setServerVariables(std::move(server));
799 const char *threadType = transport->getThreadTypeName();
800 server.set(s_THREAD_TYPE, threadType);
801 StackTraceNoHeap::AddExtraLogging("ThreadType", threadType);
804 std::string HttpProtocol::RecordRequest(Transport *transport) {
805 char tmpfile[PATH_MAX + 1];
806 if (RuntimeOption::RecordInput) {
807 strcpy(tmpfile, "/tmp/hphp_request_XXXXXX");
808 close(mkstemp(tmpfile));
810 ReplayTransport rt;
811 rt.recordInput(transport, tmpfile);
812 Logger::Info("request recorded in %s", tmpfile);
813 return tmpfile;
815 return "";
818 void HttpProtocol::ClearRecord(bool success, const std::string &tmpfile) {
819 if (success && RuntimeOption::ClearInputOnSuccess && !tmpfile.empty()) {
820 unlink(tmpfile.c_str());
821 Logger::Info("request %s deleted", tmpfile.c_str());
825 ///////////////////////////////////////////////////////////////////////////////
827 void HttpProtocol::DecodeParameters(Array& variables, const char *data,
828 size_t size, bool post /* = false */) {
829 if (data == nullptr || size == 0) {
830 return;
833 const char *s = data;
834 const char *e = s + size;
835 const char *p, *val;
837 while (s < e && (p = (const char *)memchr(s, '&', (e - s)))) {
838 last_value:
839 if ((val = (const char *)memchr(s, '=', (p - s)))) {
840 String sname = url_decode(s, val - s);
842 val++;
843 String value = url_decode(val, p - val);
845 register_variable(variables, (char*)sname.data(), value);
846 } else if (!post) {
847 String sname = url_decode(s, p - s);
848 register_variable(variables, (char*)sname.data(),
849 empty_string_variant_ref);
851 s = p + 1;
853 if (s < e) {
854 p = e;
855 goto last_value;
859 void HttpProtocol::DecodeCookies(Array& variables, char *data) {
860 assertx(data && *data);
862 char *strtok_buf = nullptr;
863 char *var = strtok_r(data, ";", &strtok_buf);
864 while (var) {
865 char *val = strchr(var, '=');
867 // Remove leading spaces from cookie names, needed for multi-cookie
868 // header where ; can be followed by a space */
869 while (isspace(*var)) {
870 var++;
873 if (var != val && *var != '\0') {
874 if (val) { /* have a value */
875 String sname = url_decode(var, val - var);
877 ++val;
878 String value = url_decode(val, strlen(val));
880 register_variable(variables, (char*)sname.data(), value, false);
881 } else {
882 String sname = url_decode(var, strlen(var));
884 register_variable(variables, (char*)sname.data(),
885 empty_string_variant_ref, false);
889 var = strtok_r(nullptr, ";", &strtok_buf);
893 bool HttpProtocol::IsRfc1867(const string contentType, string &boundary) {
894 if (contentType.empty()) return false;
895 const char *ctstr = contentType.c_str();
896 char *s;
897 char *e;
898 for (s = (char*)ctstr; *s && !(*s == ';' || *s == ',' || *s == ' '); s++) ;
899 if (strncasecmp(ctstr, MULTIPART_CONTENT_TYPE, s - ctstr)) {
900 return false;
902 s = strstr(s, "boundary");
903 if (!s || !(s=strchr(s, '='))) {
904 Logger::Warning("Missing boundary in multipart/form-data POST data");
905 return false;
907 s++;
908 if (s[0] == '"') {
909 s++;
910 e = strchr(s, '"');
911 if (!e) {
912 Logger::Warning("Invalid boundary in multipart/form-data POST data");
913 return false;
915 } else {
916 /* search for the end of the boundary */
917 e = strpbrk(s, ",;");
919 if (e) {
920 e[0] = '\0';
922 boundary = s;
923 return true;
926 void HttpProtocol::DecodeRfc1867(Transport *transport,
927 Array& post,
928 Array& files,
929 size_t contentLength,
930 const void *&data,
931 size_t &size,
932 string boundary) {
933 rfc1867PostHandler(transport,
934 post,
935 files,
936 contentLength,
937 data,
938 size,
939 boundary);
942 const char *HttpProtocol::GetReasonString(int code) {
943 // https://tools.ietf.org/html/rfc7231#section-6.1
944 switch (code) {
945 case 100: return "Continue";
946 case 101: return "Switching Protocols";
947 case 200: return "OK";
948 case 201: return "Created";
949 case 202: return "Accepted";
950 case 203: return "Non-Authoritative Information";
951 case 204: return "No Content";
952 case 205: return "Reset Content";
953 case 206: return "Partial Content";
954 case 300: return "Multiple Choices";
955 case 301: return "Moved Permanently";
956 case 302: return "Found";
957 case 303: return "See Other";
958 case 304: return "Not Modified";
959 case 305: return "Use Proxy";
960 case 307: return "Temporary Redirect";
961 case 400: return "Bad Request";
962 case 401: return "Unauthorized";
963 case 402: return "Payment Required";
964 case 403: return "Forbidden";
965 case 404: return "Not Found";
966 case 405: return "Method Not Allowed";
967 case 406: return "Not Acceptable";
968 case 407: return "Proxy Authentication Required";
969 case 408: return "Request Timeout";
970 case 409: return "Conflict";
971 case 410: return "Gone";
972 case 411: return "Length Required";
973 case 412: return "Precondition Failed";
974 case 413: return "Payload Too Large";
975 case 414: return "URI Too Long";
976 case 415: return "Unsupported Media Type";
977 case 416: return "Range Not Satisfiable";
978 case 417: return "Expectation Failed";
979 case 500: return "Internal Server Error";
980 case 501: return "Not Implemented";
981 case 502: return "Bad Gateway";
982 case 503: return "Service Unavailable";
983 case 504: return "Gateway Timeout";
984 case 505: return "HTTP Version Not Supported";
986 return "";
989 ///////////////////////////////////////////////////////////////////////////////
991 bool HttpProtocol::ProxyRequest(Transport *transport, bool force,
992 const std::string &url,
993 int &code, std::string &error,
994 StringBuffer &response,
995 HeaderMap *extraHeaders /* = NULL */) {
996 assertx(transport);
997 if (transport->headersSent()) {
998 raise_warning("Cannot proxy request - headers already sent");
999 return false;
1002 HeaderMap requestHeaders;
1003 transport->getHeaders(requestHeaders);
1004 if (extraHeaders) {
1005 for (HeaderMap::const_iterator iter = extraHeaders->begin();
1006 iter != extraHeaders->end(); ++iter) {
1007 std::vector<std::string> &values = requestHeaders[iter->first];
1008 values.insert(values.end(), iter->second.begin(), iter->second.end());
1012 size_t size = 0;
1013 const char *data = nullptr;
1014 if (transport->getMethod() == Transport::Method::POST) {
1015 data = (const char *)transport->getPostData(size);
1018 req::vector<String> responseHeaders;
1019 HttpClient http;
1020 code = http.request(transport->getMethodName(),
1021 url.c_str(), data, size, response, &requestHeaders,
1022 &responseHeaders);
1024 if (code == 0) {
1025 if (!force) return false; // so we can retry
1026 Logger::Error("Unable to proxy %s: %s", url.c_str(),
1027 http.getLastError().c_str());
1028 error = http.getLastError();
1029 return true;
1032 for (unsigned int i = 0; i < responseHeaders.size(); i++) {
1033 String &header = responseHeaders[i];
1034 if (header.find(":") != String::npos &&
1035 header.find("Content-Length: ") != 0 &&
1036 header.find("Client-Transfer-Encoding: ") != 0 &&
1037 header.find("Transfer-Encoding: ") != 0 &&
1038 header.find("Connection: ") != 0) {
1039 transport->addHeader(header.data());
1042 const char* respData = response.data();
1043 if (!respData) {
1044 respData = "";
1046 Logger::Verbose("Response code was %d when proxying %s", code, url.c_str());
1047 return true;
1050 ///////////////////////////////////////////////////////////////////////////////