2 +----------------------------------------------------------------------+
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"
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"
52 ///////////////////////////////////////////////////////////////////////////////
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
);
61 const void *extra
= transport
->getMorePostData(delta
);
62 if (size
+ delta
< VirtualHost::GetMaxPostSize()) {
63 data
= buffer_append(data
, size
, extra
, delta
);
66 } while (transport
->hasMorePostData());
72 static void CopyParams(Array
& dest
, const Array
& src
) {
75 [&](Cell k
, TypedValue v
) {
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
);
93 VirtualHost::SetCurrent(nullptr);
94 return VirtualHost::GetCurrent();
98 s_REQUEST_START_TIME("REQUEST_START_TIME"),
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"),
121 s__SERVER("_SERVER"),
124 s__REQUEST("_REQUEST"),
126 s__COOKIE("_COOKIE"),
127 s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
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"),
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"),
157 static void PrepareEnv(Array
& env
, Transport
*transport
) {
159 process_env_variables(env
);
162 if (RuntimeOption::EvalJit
) {
163 env
.set(s_HHVM_JIT
, 1);
167 env
.set(s_HHVM_ARCH
, "x64");
170 env
.set(s_HHVM_ARCH
, "arm");
173 env
.set(s_HHVM_ARCH
, "ppc64");
178 RuntimeOption::ServerExecutionMode() && !is_cli_mode();
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));
199 struct timeval tp
= {0};
201 if (!gettimeofday(&tp
, nullptr)) {
202 now_double
= (double)(tp
.tv_sec
+ tp
.tv_usec
/ 1000000.00);
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
,
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
) {
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());
245 auto name##arr = empty_darray(); \
246 SCOPE_EXIT { php_global_set(s__##name, name##arr); };
258 Variant HTTP_RAW_POST_DATA
;
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
) {
281 PrepareEnv(ENVarr
, transport
);
285 if (!r
.queryString().empty()) {
286 PrepareGetVariable(GETarr
, r
);
291 postPopulated
= true;
292 PreparePostVariables(POSTarr
, HTTP_RAW_POST_DATA
,
293 FILESarr
, transport
, r
);
297 PrepareCookieVariable(COOKIEarr
, transport
);
301 StartRequest(SERVERarr
);
302 PrepareServerVariable(SERVERarr
,
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
,
326 void HttpProtocol::PrepareRequestVariables(Array
& request
,
330 const std::string
& requestOrder
) {
331 for (const char& c
: requestOrder
) {
335 CopyParams(request
, get
);
339 CopyParams(request
, post
);
343 CopyParams(request
, cookie
);
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
,
360 Transport
*transport
,
361 const RequestURI
& r
) {
362 if (transport
->getMethod() != Transport::Method::POST
) {
366 std::string contentType
= transport
->getHeader("Content-Type");
367 std::string contentLength
= transport
->getHeader("Content-Length");
369 bool needDelete
= false;
371 const void *data
= transport
->getPostData(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
;
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()) {
386 transport
->getMorePostData(delta
);
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
) {
400 data
= buffer_duplicate(data
, size
);
405 DecodeRfc1867(transport
,
417 assertx(!transport
->getFiles(files_str
));
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;
426 auto vhost
= VirtualHost::GetCurrent();
427 if (vhost
&& vhost
->alwaysDecodePostData(r
.originalURL())) {
433 DecodeParameters(post
, (const char*)data
, size
, true);
436 bool ret
= transport
->getFiles(files_str
);
438 files
= unserialize_from_string(
440 VariableUnserializer::Type::Serialize
448 if (size
> StringData::MaxSize
) {
449 // Can't store it anywhere
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()) {
472 sb
.append(cookie_data
);
473 DecodeCookies(cookie
, (char*)sb
.data());
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
,
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
,
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());
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.
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
,
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());
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();
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()));
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());
666 name
= name
.substr(0, pos
);
669 if (r
.defaultDoc()) {
670 if (!name
.empty() && name
[name
.length() - 1] != '/') {
673 name
+= String(RuntimeOption::DefaultDocument
);
675 server
.set(s_SCRIPT_NAME
, name
);
677 server
.set(s_SCRIPT_NAME
, r
.resolvedURL());
681 server
.set(s_PHP_SELF
, r
.originalURL());
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());
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
));
711 server
.set(s_PATH_TRANSLATED
,
712 String(server
[s_DOCUMENT_ROOT
].toCStrRef() +
713 s_forwardslash
+ pathTranslated
));
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
);
731 server
.set(s_REQUEST_METHOD
, transport
->getExtendedMethod());
735 server
.set(s_REQUEST_METHOD
, empty_string_tv()); break;
737 if (transport
->isSSL()) {
738 server
.set(s_HTTPS
, s_on
);
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
,
751 const SourceRootInfo
&sri
,
752 const VirtualHost
*vhost
) {
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
);
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
);
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
));
807 rt
.recordInput(transport
, tmpfile
);
808 Logger::Info("request recorded in %s", tmpfile
);
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) {
829 const char *s
= data
;
830 const char *e
= s
+ size
;
833 while (s
< e
&& (p
= (const char *)memchr(s
, '&', (e
- s
)))) {
835 if ((val
= (const char *)memchr(s
, '=', (p
- s
)))) {
836 String sname
= url_decode(s
, val
- s
);
839 String value
= url_decode(val
, p
- val
);
841 register_variable(variables
, (char*)sname
.data(), value
);
843 String sname
= url_decode(s
, p
- s
);
844 register_variable(variables
, (char*)sname
.data(), empty_string());
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
);
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
)) {
868 if (var
!= val
&& *var
!= '\0') {
869 if (val
) { /* have a value */
870 String sname
= url_decode(var
, val
- var
);
873 String value
= url_decode(val
, strlen(val
));
875 register_variable(variables
, (char*)sname
.data(), value
, false);
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();
893 for (s
= (char*)ctstr
; *s
&& !(*s
== ';' || *s
== ',' || *s
== ' '); s
++) ;
894 if (strncasecmp(ctstr
, MULTIPART_CONTENT_TYPE
, s
- ctstr
)) {
897 s
= strstr(s
, "boundary");
898 if (!s
|| !(s
=strchr(s
, '='))) {
899 Logger::Warning("Missing boundary in multipart/form-data POST data");
907 Logger::Warning("Invalid boundary in multipart/form-data POST data");
911 /* search for the end of the boundary */
912 e
= strpbrk(s
, ",;");
921 void HttpProtocol::DecodeRfc1867(Transport
*transport
,
924 size_t contentLength
,
927 std::string boundary
) {
928 rfc1867PostHandler(transport
,
937 const char *HttpProtocol::GetReasonString(int code
) {
938 // https://tools.ietf.org/html/rfc7231#section-6.1
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";
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 */) {
992 if (transport
->headersSent()) {
993 raise_warning("Cannot proxy request - headers already sent");
997 auto requestHeaders
= transport
->getHeaders();
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());
1007 const char *data
= nullptr;
1008 if (transport
->getMethod() == Transport::Method::POST
) {
1009 data
= (const char *)transport
->getPostData(size
);
1012 req::vector
<String
> responseHeaders
;
1014 code
= http
.request(transport
->getMethodName(),
1015 url
.c_str(), data
, size
, response
, &requestHeaders
,
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();
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();
1040 Logger::Verbose("Response code was %d when proxying %s", code
, url
.c_str());
1044 ///////////////////////////////////////////////////////////////////////////////