Create dedicate crate for php_escaping.rs
[hiphop-php.git] / hphp / runtime / server / http-protocol.cpp
blob3c5a291b6a2926604b7cb91a27f1e749f5bd91b6
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 <string>
20 #include <folly/Conv.h>
21 #include <folly/portability/SysTime.h>
23 #include "hphp/util/arch.h"
24 #include "hphp/util/logger.h"
25 #include "hphp/util/stack-trace.h"
26 #include "hphp/util/text-util.h"
28 #include "hphp/runtime/base/array-init.h"
29 #include "hphp/runtime/base/array-iterator.h"
30 #include "hphp/runtime/base/builtin-functions.h"
31 #include "hphp/runtime/base/http-client.h"
32 #include "hphp/runtime/base/php-globals.h"
33 #include "hphp/runtime/base/program-functions.h"
34 #include "hphp/runtime/base/request-injection-data.h"
35 #include "hphp/runtime/base/runtime-option.h"
36 #include "hphp/runtime/base/string-util.h"
37 #include "hphp/runtime/base/zend-string.h"
38 #include "hphp/runtime/base/zend-url.h"
39 #include "hphp/runtime/ext/string/ext_string.h"
40 #include "hphp/runtime/server/cli-server.h"
41 #include "hphp/runtime/server/replay-transport.h"
42 #include "hphp/runtime/server/request-uri.h"
43 #include "hphp/runtime/server/source-root-info.h"
44 #include "hphp/runtime/server/transport.h"
45 #include "hphp/runtime/server/upload.h"
46 #include "hphp/runtime/server/virtual-host.h"
47 #include "hphp/runtime/vm/globals-array.h"
49 #define DEFAULT_POST_CONTENT_TYPE "application/x-www-form-urlencoded"
51 namespace HPHP {
52 ///////////////////////////////////////////////////////////////////////////////
53 // helper functions
55 static bool read_all_post_data(Transport *transport,
56 const void *&data, size_t &size) {
57 if (transport->hasMorePostData()) {
58 data = buffer_duplicate(data, size);
59 do {
60 size_t delta = 0;
61 const void *extra = transport->getMorePostData(delta);
62 if (size + delta < VirtualHost::GetMaxPostSize()) {
63 data = buffer_append(data, size, extra, delta);
64 size += delta;
66 } while (transport->hasMorePostData());
67 return true;
69 return false;
72 static void CopyParams(Array& dest, const Array& src) {
73 IterateKVNoInc(
74 src.get(),
75 [&](Cell k, TypedValue v) {
76 const auto arraykey =
77 dest.convertKey<IntishCast::Cast>(k);
78 dest.set(arraykey, v, true);
83 ///////////////////////////////////////////////////////////////////////////////
85 const VirtualHost *HttpProtocol::GetVirtualHost(Transport *transport) {
86 if (!RuntimeOption::VirtualHosts.empty()) {
87 std::string host = transport->getHeader("Host");
88 if (auto vh = VirtualHost::Resolve(host)) {
89 VirtualHost::SetCurrent(vh);
90 return vh;
93 VirtualHost::SetCurrent(nullptr);
94 return VirtualHost::GetCurrent();
97 const StaticString
98 s_REQUEST_START_TIME("REQUEST_START_TIME"),
99 s_HPHP("HPHP"),
100 s_HHVM("HHVM"),
101 s_HHVM_JIT("HHVM_JIT"),
102 s_HHVM_ARCH("HHVM_ARCH"),
103 s_HPHP_SERVER("HPHP_SERVER"),
104 s_HPHP_HOTPROFILER("HPHP_HOTPROFILER"),
105 s_HTTP_HOST("HTTP_HOST"),
106 s_CONTENT_TYPE("CONTENT_TYPE"),
107 s_CONTENT_LENGTH("CONTENT_LENGTH"),
108 s_PHP_AUTH_USER("PHP_AUTH_USER"),
109 s_PHP_AUTH_PW("PHP_AUTH_PW"),
110 s_PHP_AUTH_DIGEST("PHP_AUTH_DIGEST"),
111 s_REQUEST_URI("REQUEST_URI"),
112 s_SCRIPT_URL("SCRIPT_URL"),
113 s_SCRIPT_URI("SCRIPT_URI"),
114 s_SCRIPT_NAME("SCRIPT_NAME"),
115 s_PHP_SELF("PHP_SELF"),
116 s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
117 s_PATH_TRANSLATED("PATH_TRANSLATED"),
118 s_PATH_INFO("PATH_INFO"),
119 s_argc("argc"),
120 s_argv("argv"),
121 s__SERVER("_SERVER"),
122 s__GET("_GET"),
123 s__POST("_POST"),
124 s__REQUEST("_REQUEST"),
125 s__ENV("_ENV"),
126 s__COOKIE("_COOKIE"),
127 s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
128 s__FILES("_FILES"),
129 s_GATEWAY_INTERFACE("GATEWAY_INTERFACE"),
130 s_CGI_1_1("CGI/1.1"),
131 s_SERVER_ADDR("SERVER_ADDR"),
132 s_SERVER_NAME("SERVER_NAME"),
133 s_SERVER_PORT("SERVER_PORT"),
134 s_SERVER_SOFTWARE("SERVER_SOFTWARE"),
135 s_SERVER_PROTOCOL("SERVER_PROTOCOL"),
136 s_SERVER_ADMIN("SERVER_ADMIN"),
137 s_SERVER_SIGNATURE("SERVER_SIGNATURE"),
138 s_REQUEST_METHOD("REQUEST_METHOD"),
139 s_GET("GET"),
140 s_HEAD("HEAD"),
141 s_POST("POST"),
142 s_HTTPS("HTTPS"),
143 s_on("on"),
144 s_REQUEST_TIME("REQUEST_TIME"),
145 s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
146 s_QUERY_STRING("QUERY_STRING"),
147 s_REMOTE_ADDR("REMOTE_ADDR"),
148 s_REMOTE_HOST("REMOTE_HOST"),
149 s_REMOTE_PORT("REMOTE_PORT"),
150 s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
151 s_THREAD_TYPE("THREAD_TYPE"),
152 s_dash("-"),
153 s_underscore("_"),
154 s_HTTP_("HTTP_"),
155 s_forwardslash("/");
157 static void PrepareEnv(Array& env, Transport *transport) {
158 // $_ENV
159 process_env_variables(env);
160 env.set(s_HPHP, 1);
161 env.set(s_HHVM, 1);
162 if (RuntimeOption::EvalJit) {
163 env.set(s_HHVM_JIT, 1);
165 switch (arch()) {
166 case Arch::X64:
167 env.set(s_HHVM_ARCH, "x64");
168 break;
169 case Arch::ARM:
170 env.set(s_HHVM_ARCH, "arm");
171 break;
172 case Arch::PPC64:
173 env.set(s_HHVM_ARCH, "ppc64");
174 break;
177 bool isServer =
178 RuntimeOption::ServerExecutionMode() && !is_cli_mode();
179 if (isServer) {
180 env.set(s_HPHP_SERVER, 1);
181 env.set(s_HPHP_HOTPROFILER, 1);
184 // Do this last so it can overwrite all the previous settings
185 HeaderMap transportParams;
186 transport->getTransportParams(transportParams);
187 for (auto const& header : transportParams) {
188 String key(header.first);
189 String value(header.second.back());
190 g_context->setenv(key, value);
191 env.set(env.convertKey<IntishCast::Cast>(key),
192 make_tv<KindOfString>(value.get()));
196 static void StartRequest(Array& server) {
197 server.set(s_REQUEST_START_TIME, time(nullptr));
198 time_t now;
199 struct timeval tp = {0};
200 double now_double;
201 if (!gettimeofday(&tp, nullptr)) {
202 now_double = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
203 now = tp.tv_sec;
204 } else {
205 now = time(nullptr);
206 now_double = (double)now;
208 server.set(s_REQUEST_TIME, now);
209 server.set(s_REQUEST_TIME_FLOAT, now_double);
213 * PHP has "EGPCS" processing order of these global variables, and this
214 * order is important in preparing $_REQUEST that needs to know which to
215 * overwrite what when name happens to be the same.
217 void HttpProtocol::PrepareSystemVariables(Transport *transport,
218 const RequestURI &r,
219 const SourceRootInfo &sri) {
220 auto const vhost = VirtualHost::GetCurrent();
221 auto const emptyArr = empty_darray();
222 php_global_set(s__SERVER, emptyArr);
223 php_global_set(s__GET, emptyArr);
224 php_global_set(s__POST, emptyArr);
225 php_global_set(s__FILES, emptyArr);
226 php_global_set(s__REQUEST, emptyArr);
227 php_global_set(s__ENV, emptyArr);
228 php_global_set(s__COOKIE, emptyArr);
230 // according to doc if content type is multipart/form-data
231 // $HTTP_RAW_POST_DATA should always not available
232 bool shouldSetHttpRawPostData = false;
233 if (RuntimeOption::AlwaysPopulateRawPostData) {
234 std::string dummy;
235 if (!IsRfc1867(transport->getHeader("Content-Type"), dummy)) {
236 shouldSetHttpRawPostData = true;
240 if (shouldSetHttpRawPostData) {
241 php_global_set(s_HTTP_RAW_POST_DATA, empty_string());
244 #define X(name) \
245 auto name##arr = empty_darray(); \
246 SCOPE_EXIT { php_global_set(s__##name, name##arr); };
248 X(ENV)
249 X(GET)
250 X(POST)
251 X(COOKIE)
252 X(FILES)
253 X(SERVER)
254 X(REQUEST)
256 #undef X
258 Variant HTTP_RAW_POST_DATA;
259 SCOPE_EXIT {
260 if (shouldSetHttpRawPostData) {
261 php_global_set(s_HTTP_RAW_POST_DATA, std::move(HTTP_RAW_POST_DATA));
265 auto variablesOrder = RequestInfo::s_requestInfo.getNoCheck()
266 ->m_reqInjectionData.getVariablesOrder();
268 auto requestOrder = RequestInfo::s_requestInfo.getNoCheck()
269 ->m_reqInjectionData.getRequestOrder();
271 if (requestOrder.empty()) {
272 requestOrder = variablesOrder;
275 bool postPopulated = false;
277 for (char& c : variablesOrder) {
278 switch(c) {
279 case 'e':
280 case 'E':
281 PrepareEnv(ENVarr, transport);
282 break;
283 case 'g':
284 case 'G':
285 if (!r.queryString().empty()) {
286 PrepareGetVariable(GETarr, r);
288 break;
289 case 'p':
290 case 'P':
291 postPopulated = true;
292 PreparePostVariables(POSTarr, HTTP_RAW_POST_DATA,
293 FILESarr, transport, r);
294 break;
295 case 'c':
296 case 'C':
297 PrepareCookieVariable(COOKIEarr, transport);
298 break;
299 case 's':
300 case 'S':
301 StartRequest(SERVERarr);
302 PrepareServerVariable(SERVERarr,
303 transport,
305 sri,
306 vhost);
307 break;
311 if (!postPopulated && shouldSetHttpRawPostData) {
312 // Always try to populate $HTTP_RAW_POST_DATA if not populated
313 auto dummyPost = empty_darray();
314 auto dummyFiles = empty_darray();
315 PreparePostVariables(dummyPost, HTTP_RAW_POST_DATA,
316 dummyFiles, transport, r);
319 PrepareRequestVariables(REQUESTarr,
320 GETarr,
321 POSTarr,
322 COOKIEarr,
323 requestOrder);
326 void HttpProtocol::PrepareRequestVariables(Array& request,
327 const Array& get,
328 const Array& post,
329 const Array& cookie,
330 const std::string& requestOrder) {
331 for (const char& c : requestOrder) {
332 switch(c) {
333 case 'g':
334 case 'G':
335 CopyParams(request, get);
336 break;
337 case 'p':
338 case 'P':
339 CopyParams(request, post);
340 break;
341 case 'c':
342 case 'C':
343 CopyParams(request, cookie);
344 break;
350 void HttpProtocol::PrepareGetVariable(Array& get,
351 const RequestURI &r) {
352 DecodeParameters(get,
353 r.queryString().data(),
354 r.queryString().size());
357 void HttpProtocol::PreparePostVariables(Array& post,
358 Variant& raw_post,
359 Array& files,
360 Transport *transport,
361 const RequestURI& r) {
362 if (transport->getMethod() != Transport::Method::POST) {
363 return;
366 std::string contentType = transport->getHeader("Content-Type");
367 std::string contentLength = transport->getHeader("Content-Length");
369 bool needDelete = false;
370 size_t size = 0;
371 const void *data = transport->getPostData(size);
372 if (data && size) {
373 std::string boundary;
374 auto content_length = strtoll(contentLength.c_str(), nullptr, 10);
375 bool rfc1867Post = IsRfc1867(contentType, boundary);
376 std::string files_str;
377 if (rfc1867Post) {
378 if (content_length < 0 ||
379 content_length > VirtualHost::GetMaxPostSize()) {
380 // $_POST and $_FILES are empty
381 Logger::Warning("POST Content-Length of %lld bytes exceeds "
382 "the limit of %" PRId64 " bytes",
383 content_length, VirtualHost::GetMaxPostSize());
384 while (transport->hasMorePostData()) {
385 size_t delta = 0;
386 transport->getMorePostData(delta);
388 data = nullptr;
389 size = 0;
390 } else {
391 // content_length is a reasonable nonnegative size.
392 bool invalidate = false;
393 if (transport->hasMorePostData()) {
394 // Calls to getMorePostData may invalidate data, so make a copy
395 // iff we're trying to coalesce the entire POST body. Otherwise,
396 // data may be invalid when DecodeRfc1867 returns. See
397 // upload.cpp:read_post.
398 if (RuntimeOption::AlwaysPopulateRawPostData) {
399 needDelete = true;
400 data = buffer_duplicate(data, size);
401 } else {
402 invalidate = true;
405 DecodeRfc1867(transport,
406 post,
407 files,
408 content_length,
409 data,
410 size,
411 boundary);
412 if (invalidate) {
413 data = nullptr;
414 size = 0;
417 assertx(!transport->getFiles(files_str));
418 } else {
419 needDelete = read_all_post_data(transport, data, size);
421 bool decodeData = strncasecmp(contentType.c_str(),
422 DEFAULT_POST_CONTENT_TYPE,
423 sizeof(DEFAULT_POST_CONTENT_TYPE)-1) == 0;
425 if (!decodeData) {
426 auto vhost = VirtualHost::GetCurrent();
427 if (vhost && vhost->alwaysDecodePostData(r.originalURL())) {
428 decodeData = true;
432 if (decodeData) {
433 DecodeParameters(post, (const char*)data, size, true);
436 bool ret = transport->getFiles(files_str);
437 if (ret) {
438 files = unserialize_from_string(
439 files_str,
440 VariableUnserializer::Type::Serialize
445 if (!data) {
446 return;
448 if (size > StringData::MaxSize) {
449 // Can't store it anywhere
450 if (needDelete) {
451 free((void*) data);
453 } else {
454 auto string_data = needDelete ?
455 String((char*)data, size, AttachString) :
456 String((char*)data, size, CopyString);
457 g_context->setRawPostData(string_data);
458 if (RuntimeOption::AlwaysPopulateRawPostData || ! needDelete) {
459 // For literal we disregard RuntimeOption::AlwaysPopulateRawPostData
460 raw_post = string_data;
466 bool HttpProtocol::PrepareCookieVariable(Array& cookie,
467 Transport *transport) {
469 std::string cookie_data = transport->getHeader("Cookie");
470 if (!cookie_data.empty()) {
471 StringBuffer sb;
472 sb.append(cookie_data);
473 DecodeCookies(cookie, (char*)sb.data());
474 return true;
475 } else {
476 return false;
480 static void CopyHeaderVariables(Array& server,
481 const HeaderMap& headers) {
482 static std::atomic<int> badRequests(-1);
484 std::vector<std::string> badHeaders;
485 for (auto const& header : headers) {
486 auto const& key = header.first;
487 auto const& values = header.second;
488 auto normalizedKey = s_HTTP_ +
489 string_replace(HHVM_FN(strtoupper)(key), s_dash,
490 s_underscore);
492 // Detect suspicious headers. We are about to modify header names for
493 // the SERVER variable. This means that it is possible to deliberately
494 // cause a header collision, which an attacker could use to sneak a
495 // header past a proxy that would either overwrite or filter it
496 // otherwise. Client code should use apache_request_headers() to
497 // retrieve the original headers if they are security-critical.
498 if (RuntimeOption::LogHeaderMangle != 0 && server.exists(normalizedKey)) {
499 badHeaders.push_back(key);
502 if (!values.empty()) {
503 // When a header has multiple values, we always take the last one.
504 server.set(normalizedKey, String(values.back()));
508 if (!badHeaders.empty()) {
509 auto reqId = badRequests.fetch_add(1, std::memory_order_acq_rel) + 1;
510 if (!(reqId % RuntimeOption::LogHeaderMangle)) {
511 std::string badNames = folly::join(", ", badHeaders);
512 std::string allHeaders;
514 const char* separator = "";
515 for (auto const& header : headers) {
516 for (auto const& value : header.second) {
517 folly::toAppend(separator, header.first, ": ", value,
518 &allHeaders);
519 separator = "\n";
523 Logger::Warning(
524 "HeaderMangle warning: "
525 "The header(s) [%s] overwrote other headers which mapped to the same "
526 "key. This happens because PHP normalises - to _, ie AN_EXAMPLE "
527 "and AN-EXAMPLE are equivalent. You should treat this as "
528 "malicious. All headers from this request:\n%s",
529 badNames.c_str(), allHeaders.c_str());
534 static void CopyTransportParams(Array& server, Transport* transport) {
535 HeaderMap transportParams;
536 // Get additional server params from the transport if it has any. In the case
537 // of fastcgi this is basically a full header list from apache/nginx.
538 transport->getTransportParams(transportParams);
539 for (auto const& header : transportParams) {
540 // These overwrite anything already set in the $_SERVER
541 // When a header has multiple values, we always take the last one.
542 server.set(String(header.first), String(header.second.back()));
546 static void CopyServerInfo(Array& server,
547 Transport *transport,
548 const VirtualHost *vhost) {
550 std::string hostHeader = transport->getHeader("Host");
551 String hostName(vhost->serverName(hostHeader));
552 String serverNameHeader(transport->getServerName());
553 if (hostHeader.empty()) {
554 server.set(s_HTTP_HOST, hostName);
555 StackTraceNoHeap::AddExtraLogging("Server", hostName.data());
556 } else {
557 // reset the HTTP_HOST header from apache.
558 server.set(s_HTTP_HOST, hostHeader);
559 StackTraceNoHeap::AddExtraLogging("Server", hostHeader.c_str());
562 // Use the header from the transport if it is available
563 if (!serverNameHeader.empty()) {
564 hostName = serverNameHeader;
565 } else if (hostName.empty() || RuntimeOption::ForceServerNameToHeader) {
566 hostName = hostHeader;
569 // _SERVER['SERVER_NAME'] shouldn't contain the port number
570 int colonPos = hostName.find(':');
571 if (colonPos != String::npos) {
572 hostName = hostName.substr(0, colonPos);
575 StackTraceNoHeap::AddExtraLogging("Server_SERVER_NAME", hostName.data());
577 server.set(s_GATEWAY_INTERFACE, s_CGI_1_1);
578 server.set(s_SERVER_ADDR, transport->getServerAddr());
579 server.set(s_SERVER_NAME, hostName);
580 server.set(s_SERVER_PORT, transport->getServerPort());
581 server.set(s_SERVER_SOFTWARE, transport->getServerSoftware());
582 server.set(s_SERVER_PROTOCOL, "HTTP/" + transport->getHTTPVersion());
583 server.set(s_SERVER_ADMIN, empty_string_tv());
584 server.set(s_SERVER_SIGNATURE, empty_string_tv());
587 static void CopyRemoteInfo(Array& server, Transport *transport) {
588 String remoteAddr(transport->getRemoteAddr(), CopyString);
589 String remoteHost(transport->getRemoteHost(), CopyString);
590 if (remoteAddr.empty()) {
591 remoteAddr = remoteHost;
593 // Always set remoteAddr, even if it is empty
594 server.set(s_REMOTE_ADDR, remoteAddr);
595 if (!remoteHost.empty()) {
596 server.set(s_REMOTE_HOST, remoteHost);
598 server.set(s_REMOTE_PORT, transport->getRemotePort());
601 static void CopyAuthInfo(Array& server, Transport *transport) {
602 // APE processes Authorization: Basic into PHP_AUTH_USER and PHP_AUTH_PW
603 std::string authorization = transport->getHeader("Authorization");
604 if (!authorization.empty()) {
605 if (strncmp(authorization.c_str(), "Basic ", 6) == 0) {
606 // it's safe to pass this as a string literal since authorization
607 // outlives decodedAuth; this saves us a superfluous copy.
608 String decodedAuth =
609 StringUtil::Base64Decode(String(authorization.c_str() + 6));
610 int colonPos = decodedAuth.find(':');
611 if (colonPos != String::npos) {
612 server.set(s_PHP_AUTH_USER, decodedAuth.substr(0, colonPos));
613 server.set(s_PHP_AUTH_PW, decodedAuth.substr(colonPos + 1));
615 } else if (strncmp(authorization.c_str(), "Digest ", 7) == 0) {
616 server.set(s_PHP_AUTH_DIGEST, String(authorization.c_str() + 7));
621 static void CopyPathInfo(Array& server,
622 Transport *transport,
623 const RequestURI& r,
624 const VirtualHost *vhost) {
625 server.set(s_REQUEST_URI, String(transport->getUrl(), CopyString));
626 server.set(s_SCRIPT_URL, r.originalURL());
627 String prefix(transport->isSSL() ? "https://" : "http://");
629 // Need to append port
630 assertx(server.exists(s_SERVER_PORT));
631 std::string serverPort = "80";
632 if (server.exists(s_SERVER_PORT)) {
633 Variant port = server[s_SERVER_PORT];
634 always_assert(port.isInteger() || port.isString());
635 if (port.isInteger()) {
636 serverPort = folly::to<std::string>(port.toInt32());
637 } else {
638 serverPort = port.toString().data();
642 String port_suffix("");
643 if (!transport->isSSL() && serverPort != "80") {
644 port_suffix = folly::format(":{}", serverPort).str();
647 std::string hostHeader;
648 if (server.exists(s_HTTP_HOST)) {
649 hostHeader = server[s_HTTP_HOST].toCStrRef().data();
651 String hostName;
652 if (server.exists(s_SERVER_NAME)) {
653 assertx(server[s_SERVER_NAME].isString());
654 hostName = server[s_SERVER_NAME].toCStrRef();
656 server.set(s_SCRIPT_URI,
657 String(prefix + (hostHeader.empty() ? hostName + port_suffix :
658 String(hostHeader)) + r.originalURL()));
660 if (r.rewritten()) {
661 // when URL is rewritten, PHP decided to put original URL as SCRIPT_NAME
662 String name = r.originalURL();
663 if (!r.pathInfo().empty()) {
664 int pos = name.find(r.pathInfo());
665 if (pos >= 0) {
666 name = name.substr(0, pos);
669 if (r.defaultDoc()) {
670 if (!name.empty() && name[name.length() - 1] != '/') {
671 name += "/";
673 name += String(RuntimeOption::DefaultDocument);
675 server.set(s_SCRIPT_NAME, name);
676 } else {
677 server.set(s_SCRIPT_NAME, r.resolvedURL());
680 if (r.rewritten()) {
681 server.set(s_PHP_SELF, r.originalURL());
682 } else {
683 server.set(s_PHP_SELF, r.resolvedURL() + r.origPathInfo());
686 String documentRoot = transport->getDocumentRoot();
687 if (documentRoot.empty()) {
688 // Right now this is just RuntimeOption::SourceRoot but mwilliams wants to
689 // fix it so it is settable, so I'll leave this for now
690 documentRoot = vhost->getDocumentRoot();
692 if (documentRoot != s_forwardslash &&
693 documentRoot[documentRoot.length() - 1] == '/') {
694 documentRoot = documentRoot.substr(0, documentRoot.length() - 1);
696 server.set(s_DOCUMENT_ROOT, documentRoot);
697 server.set(s_SCRIPT_FILENAME, r.absolutePath());
699 if (r.pathInfo().empty()) {
700 server.set(s_PATH_TRANSLATED, r.absolutePath());
701 } else {
702 assertx(server.exists(s_DOCUMENT_ROOT));
703 assertx(server[s_DOCUMENT_ROOT].isString());
704 // reset path_translated back to the transport if it has it.
705 auto const& pathTranslated = transport->getPathTranslated();
706 if (!pathTranslated.empty()) {
707 if (documentRoot == s_forwardslash) {
708 // path outside document root or / is document root
709 server.set(s_PATH_TRANSLATED, String(pathTranslated));
710 } else {
711 server.set(s_PATH_TRANSLATED,
712 String(server[s_DOCUMENT_ROOT].toCStrRef() +
713 s_forwardslash + pathTranslated));
715 } else {
716 server.set(s_PATH_TRANSLATED,
717 String(server[s_DOCUMENT_ROOT].toCStrRef() +
718 server[s_SCRIPT_NAME].toCStrRef() +
719 r.pathInfo().data()));
721 server.set(s_PATH_INFO, r.pathInfo());
724 switch (transport->getMethod()) {
725 case Transport::Method::GET: server.set(s_REQUEST_METHOD, s_GET); break;
726 case Transport::Method::HEAD: server.set(s_REQUEST_METHOD, s_HEAD); break;
727 case Transport::Method::POST:
728 if (transport->getExtendedMethod() == nullptr) {
729 server.set(s_REQUEST_METHOD, s_POST);
730 } else {
731 server.set(s_REQUEST_METHOD, transport->getExtendedMethod());
733 break;
734 default:
735 server.set(s_REQUEST_METHOD, empty_string_tv()); break;
737 if (transport->isSSL()) {
738 server.set(s_HTTPS, s_on);
739 } else {
740 server.set(s_HTTPS, empty_string_tv());
742 server.set(s_QUERY_STRING, r.queryString());
744 server.set(s_argv, make_varray(r.queryString()));
745 server.set(s_argc, 1);
748 void HttpProtocol::PrepareServerVariable(Array& server,
749 Transport *transport,
750 const RequestURI &r,
751 const SourceRootInfo &sri,
752 const VirtualHost *vhost) {
753 // $_SERVER
755 std::string contentType = transport->getHeader("Content-Type");
756 std::string contentLength = transport->getHeader("Content-Length");
758 // HTTP_ headers -- we don't exclude headers we handle elsewhere (e.g.,
759 // Content-Type, Authorization), since the CGI "spec" merely says the server
760 // "may" exclude them; this is not what APE does, but it's harmless.
761 auto const& headers = transport->getHeaders();
762 // Do this first so other methods can overwrite them
763 CopyHeaderVariables(server, headers);
764 CopyServerInfo(server, transport, vhost);
765 // Do this last so it can overwrite all the previous settings
766 CopyTransportParams(server, transport);
767 CopyRemoteInfo(server, transport);
768 CopyAuthInfo(server, transport);
769 CopyPathInfo(server, transport, r, vhost);
771 // APE sets CONTENT_TYPE and CONTENT_LENGTH without HTTP_
772 if (!contentType.empty()) {
773 server.set(s_CONTENT_TYPE, String(contentType));
775 if (!contentLength.empty()) {
776 server.set(s_CONTENT_LENGTH, String(contentLength));
779 for (auto& kv : RuntimeOption::ServerVariables) {
780 String idx(kv.first);
781 const auto arrkey =
782 server.convertKey<IntishCast::Cast>(idx);
783 String str(kv.second);
784 server.set(arrkey, make_tv<KindOfString>(str.get()), true);
786 for (auto& kv : vhost->getServerVars()) {
787 String idx(kv.first);
788 const auto arrkey =
789 server.convertKey<IntishCast::Cast>(idx);
790 String str(kv.second);
791 server.set(arrkey, make_tv<KindOfString>(str.get()), true);
793 server = sri.setServerVariables(std::move(server));
795 const char *threadType = transport->getThreadTypeName();
796 server.set(s_THREAD_TYPE, threadType);
797 StackTraceNoHeap::AddExtraLogging("ThreadType", threadType);
800 std::string HttpProtocol::RecordRequest(Transport *transport) {
801 char tmpfile[PATH_MAX + 1];
802 if (RuntimeOption::RecordInput) {
803 strcpy(tmpfile, "/tmp/hphp_request_XXXXXX");
804 close(mkstemp(tmpfile));
806 ReplayTransport rt;
807 rt.recordInput(transport, tmpfile);
808 Logger::Info("request recorded in %s", tmpfile);
809 return tmpfile;
811 return "";
814 void HttpProtocol::ClearRecord(bool success, const std::string &tmpfile) {
815 if (success && RuntimeOption::ClearInputOnSuccess && !tmpfile.empty()) {
816 unlink(tmpfile.c_str());
817 Logger::Info("request %s deleted", tmpfile.c_str());
821 ///////////////////////////////////////////////////////////////////////////////
823 void HttpProtocol::DecodeParameters(Array& variables, const char *data,
824 size_t size, bool post /* = false */) {
825 if (data == nullptr || size == 0) {
826 return;
829 const char *s = data;
830 const char *e = s + size;
831 const char *p, *val;
833 while (s < e && (p = (const char *)memchr(s, '&', (e - s)))) {
834 last_value:
835 if ((val = (const char *)memchr(s, '=', (p - s)))) {
836 String sname = url_decode(s, val - s);
838 val++;
839 String value = url_decode(val, p - val);
841 register_variable(variables, (char*)sname.data(), value);
842 } else if (!post) {
843 String sname = url_decode(s, p - s);
844 register_variable(variables, (char*)sname.data(), empty_string());
846 s = p + 1;
848 if (s < e) {
849 p = e;
850 goto last_value;
854 void HttpProtocol::DecodeCookies(Array& variables, char *data) {
855 assertx(data && *data);
857 char *strtok_buf = nullptr;
858 char *var = strtok_r(data, ";", &strtok_buf);
859 while (var) {
860 char *val = strchr(var, '=');
862 // Remove leading spaces from cookie names, needed for multi-cookie
863 // header where ; can be followed by a space */
864 while (isspace(*var)) {
865 var++;
868 if (var != val && *var != '\0') {
869 if (val) { /* have a value */
870 String sname = url_decode(var, val - var);
872 ++val;
873 String value = url_decode(val, strlen(val));
875 register_variable(variables, (char*)sname.data(), value, false);
876 } else {
877 String sname = url_decode(var, strlen(var));
879 register_variable(variables, (char*)sname.data(),
880 empty_string(), false);
884 var = strtok_r(nullptr, ";", &strtok_buf);
888 bool HttpProtocol::IsRfc1867(const std::string contentType, std::string &boundary) {
889 if (contentType.empty()) return false;
890 const char *ctstr = contentType.c_str();
891 char *s;
892 char *e;
893 for (s = (char*)ctstr; *s && !(*s == ';' || *s == ',' || *s == ' '); s++) ;
894 if (strncasecmp(ctstr, MULTIPART_CONTENT_TYPE, s - ctstr)) {
895 return false;
897 s = strstr(s, "boundary");
898 if (!s || !(s=strchr(s, '='))) {
899 Logger::Warning("Missing boundary in multipart/form-data POST data");
900 return false;
902 s++;
903 if (s[0] == '"') {
904 s++;
905 e = strchr(s, '"');
906 if (!e) {
907 Logger::Warning("Invalid boundary in multipart/form-data POST data");
908 return false;
910 } else {
911 /* search for the end of the boundary */
912 e = strpbrk(s, ",;");
914 if (e) {
915 e[0] = '\0';
917 boundary = s;
918 return true;
921 void HttpProtocol::DecodeRfc1867(Transport *transport,
922 Array& post,
923 Array& files,
924 size_t contentLength,
925 const void *&data,
926 size_t &size,
927 std::string boundary) {
928 rfc1867PostHandler(transport,
929 post,
930 files,
931 contentLength,
932 data,
933 size,
934 boundary);
937 const char *HttpProtocol::GetReasonString(int code) {
938 // https://tools.ietf.org/html/rfc7231#section-6.1
939 switch (code) {
940 case 100: return "Continue";
941 case 101: return "Switching Protocols";
942 case 200: return "OK";
943 case 201: return "Created";
944 case 202: return "Accepted";
945 case 203: return "Non-Authoritative Information";
946 case 204: return "No Content";
947 case 205: return "Reset Content";
948 case 206: return "Partial Content";
949 case 300: return "Multiple Choices";
950 case 301: return "Moved Permanently";
951 case 302: return "Found";
952 case 303: return "See Other";
953 case 304: return "Not Modified";
954 case 305: return "Use Proxy";
955 case 307: return "Temporary Redirect";
956 case 400: return "Bad Request";
957 case 401: return "Unauthorized";
958 case 402: return "Payment Required";
959 case 403: return "Forbidden";
960 case 404: return "Not Found";
961 case 405: return "Method Not Allowed";
962 case 406: return "Not Acceptable";
963 case 407: return "Proxy Authentication Required";
964 case 408: return "Request Timeout";
965 case 409: return "Conflict";
966 case 410: return "Gone";
967 case 411: return "Length Required";
968 case 412: return "Precondition Failed";
969 case 413: return "Payload Too Large";
970 case 414: return "URI Too Long";
971 case 415: return "Unsupported Media Type";
972 case 416: return "Range Not Satisfiable";
973 case 417: return "Expectation Failed";
974 case 500: return "Internal Server Error";
975 case 501: return "Not Implemented";
976 case 502: return "Bad Gateway";
977 case 503: return "Service Unavailable";
978 case 504: return "Gateway Timeout";
979 case 505: return "HTTP Version Not Supported";
981 return "";
984 ///////////////////////////////////////////////////////////////////////////////
986 bool HttpProtocol::ProxyRequest(Transport *transport, bool force,
987 const std::string &url,
988 int &code, std::string &error,
989 StringBuffer &response,
990 HeaderMap *extraHeaders /* = NULL */) {
991 assertx(transport);
992 if (transport->headersSent()) {
993 raise_warning("Cannot proxy request - headers already sent");
994 return false;
997 auto requestHeaders = transport->getHeaders();
998 if (extraHeaders) {
999 for (HeaderMap::const_iterator iter = extraHeaders->begin();
1000 iter != extraHeaders->end(); ++iter) {
1001 std::vector<std::string> &values = requestHeaders[iter->first];
1002 values.insert(values.end(), iter->second.begin(), iter->second.end());
1006 size_t size = 0;
1007 const char *data = nullptr;
1008 if (transport->getMethod() == Transport::Method::POST) {
1009 data = (const char *)transport->getPostData(size);
1012 req::vector<String> responseHeaders;
1013 HttpClient http;
1014 code = http.request(transport->getMethodName(),
1015 url.c_str(), data, size, response, &requestHeaders,
1016 &responseHeaders);
1018 if (code == 0) {
1019 if (!force) return false; // so we can retry
1020 Logger::Error("Unable to proxy %s: %s", url.c_str(),
1021 http.getLastError().c_str());
1022 error = http.getLastError();
1023 return true;
1026 for (unsigned int i = 0; i < responseHeaders.size(); i++) {
1027 String &header = responseHeaders[i];
1028 if (header.find(":") != String::npos &&
1029 header.find("Content-Length: ") != 0 &&
1030 header.find("Client-Transfer-Encoding: ") != 0 &&
1031 header.find("Transfer-Encoding: ") != 0 &&
1032 header.find("Connection: ") != 0) {
1033 transport->addHeader(header.data());
1036 const char* respData = response.data();
1037 if (!respData) {
1038 respData = "";
1040 Logger::Verbose("Response code was %d when proxying %s", code, url.c_str());
1041 return true;
1044 ///////////////////////////////////////////////////////////////////////////////