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"
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"
54 ///////////////////////////////////////////////////////////////////////////////
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
);
63 const void *extra
= transport
->getMorePostData(delta
);
64 if (size
+ delta
< VirtualHost::GetMaxPostSize()) {
65 data
= buffer_append(data
, size
, extra
, delta
);
68 } while (transport
->hasMorePostData());
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
);
90 VirtualHost::SetCurrent(nullptr);
91 return VirtualHost::GetCurrent();
95 s_REQUEST_START_TIME("REQUEST_START_TIME"),
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"),
118 s__SERVER("_SERVER"),
121 s__REQUEST("_REQUEST"),
122 s__SESSION("_SESSION"),
124 s__COOKIE("_COOKIE"),
125 s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
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"),
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"),
155 static auto const s_arraysToClear
= {
165 static auto const s_arraysToUnset
= {
169 static void PrepareEnv(Array
& env
, Transport
*transport
) {
171 process_env_variables(env
);
174 if (RuntimeOption::EvalJit
) {
175 env
.set(s_HHVM_JIT
, 1);
179 env
.set(s_HHVM_ARCH
, "x64");
182 env
.set(s_HHVM_ARCH
, "arm");
185 env
.set(s_HHVM_ARCH
, "ppc64");
190 RuntimeOption::ServerExecutionMode() && !is_cli_mode();
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
);
207 static void StartRequest(Array
& server
) {
208 server
.set(s_REQUEST_START_TIME
, time(nullptr));
210 struct timeval tp
= {0};
212 if (!gettimeofday(&tp
, nullptr)) {
213 now_double
= (double)(tp
.tv_sec
+ tp
.tv_usec
/ 1000000.00);
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
,
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
) {
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);
259 Array name##arr(Array::Create()); \
260 SCOPE_EXIT { g->set(s__##name, name##arr, false); };
272 Variant HTTP_RAW_POST_DATA
;
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
) {
295 PrepareEnv(ENVarr
, transport
);
299 if (!r
.queryString().empty()) {
300 PrepareGetVariable(GETarr
, r
);
305 postPopulated
= true;
306 PreparePostVariables(POSTarr
, HTTP_RAW_POST_DATA
,
307 FILESarr
, transport
, r
);
311 PrepareCookieVariable(COOKIEarr
, transport
);
315 StartRequest(SERVERarr
);
316 PrepareServerVariable(SERVERarr
,
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
,
340 void HttpProtocol::PrepareRequestVariables(Array
& request
,
344 const std::string
& requestOrder
) {
345 for (const char& c
: requestOrder
) {
349 CopyParams(request
, get
);
353 CopyParams(request
, post
);
357 CopyParams(request
, cookie
);
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
,
374 Transport
*transport
,
375 const RequestURI
& r
) {
376 if (transport
->getMethod() != Transport::Method::POST
) {
380 std::string contentType
= transport
->getHeader("Content-Type");
381 std::string contentLength
= transport
->getHeader("Content-Length");
383 bool needDelete
= false;
385 const void *data
= transport
->getPostData(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
;
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()) {
400 transport
->getMorePostData(delta
);
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
) {
414 data
= buffer_duplicate(data
, size
);
419 DecodeRfc1867(transport
,
431 assertx(!transport
->getFiles(files_str
));
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;
440 auto vhost
= VirtualHost::GetCurrent();
441 if (vhost
&& vhost
->alwaysDecodePostData(r
.originalURL())) {
447 DecodeParameters(post
, (const char*)data
, size
, true);
450 bool ret
= transport
->getFiles(files_str
);
452 files
= unserialize_from_string(
454 VariableUnserializer::Type::Serialize
462 if (size
> StringData::MaxSize
) {
463 // Can't store it anywhere
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()) {
486 sb
.append(cookie_data
);
487 DecodeCookies(cookie
, (char*)sb
.data());
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
,
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
,
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());
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.
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
,
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());
652 serverPort
= port
.toString().data();
656 String
port_suffix("");
657 if (!transport
->isSSL() && serverPort
!= "80") {
658 port_suffix
= folly::format(":{}", serverPort
).str();
662 if (server
.exists(s_HTTP_HOST
)) {
663 hostHeader
= server
[s_HTTP_HOST
].toCStrRef().data();
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()));
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());
680 name
= name
.substr(0, pos
);
683 if (r
.defaultDoc()) {
684 if (!name
.empty() && name
[name
.length() - 1] != '/') {
687 name
+= String(RuntimeOption::DefaultDocument
);
689 server
.set(s_SCRIPT_NAME
, name
);
691 server
.set(s_SCRIPT_NAME
, r
.resolvedURL());
695 server
.set(s_PHP_SELF
, r
.originalURL());
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());
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
));
725 server
.set(s_PATH_TRANSLATED
,
726 String(server
[s_DOCUMENT_ROOT
].toCStrRef() +
727 s_forwardslash
+ pathTranslated
));
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
);
745 server
.set(s_REQUEST_METHOD
, transport
->getExtendedMethod());
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
,
762 const SourceRootInfo
&sri
,
763 const VirtualHost
*vhost
) {
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.
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
));
811 rt
.recordInput(transport
, tmpfile
);
812 Logger::Info("request recorded in %s", tmpfile
);
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) {
833 const char *s
= data
;
834 const char *e
= s
+ size
;
837 while (s
< e
&& (p
= (const char *)memchr(s
, '&', (e
- s
)))) {
839 if ((val
= (const char *)memchr(s
, '=', (p
- s
)))) {
840 String sname
= url_decode(s
, val
- s
);
843 String value
= url_decode(val
, p
- val
);
845 register_variable(variables
, (char*)sname
.data(), value
);
847 String sname
= url_decode(s
, p
- s
);
848 register_variable(variables
, (char*)sname
.data(),
849 empty_string_variant_ref
);
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
);
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
)) {
873 if (var
!= val
&& *var
!= '\0') {
874 if (val
) { /* have a value */
875 String sname
= url_decode(var
, val
- var
);
878 String value
= url_decode(val
, strlen(val
));
880 register_variable(variables
, (char*)sname
.data(), value
, false);
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();
898 for (s
= (char*)ctstr
; *s
&& !(*s
== ';' || *s
== ',' || *s
== ' '); s
++) ;
899 if (strncasecmp(ctstr
, MULTIPART_CONTENT_TYPE
, s
- ctstr
)) {
902 s
= strstr(s
, "boundary");
903 if (!s
|| !(s
=strchr(s
, '='))) {
904 Logger::Warning("Missing boundary in multipart/form-data POST data");
912 Logger::Warning("Invalid boundary in multipart/form-data POST data");
916 /* search for the end of the boundary */
917 e
= strpbrk(s
, ",;");
926 void HttpProtocol::DecodeRfc1867(Transport
*transport
,
929 size_t contentLength
,
933 rfc1867PostHandler(transport
,
942 const char *HttpProtocol::GetReasonString(int code
) {
943 // https://tools.ietf.org/html/rfc7231#section-6.1
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";
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 */) {
997 if (transport
->headersSent()) {
998 raise_warning("Cannot proxy request - headers already sent");
1002 HeaderMap requestHeaders
;
1003 transport
->getHeaders(requestHeaders
);
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());
1013 const char *data
= nullptr;
1014 if (transport
->getMethod() == Transport::Method::POST
) {
1015 data
= (const char *)transport
->getPostData(size
);
1018 req::vector
<String
> responseHeaders
;
1020 code
= http
.request(transport
->getMethodName(),
1021 url
.c_str(), data
, size
, response
, &requestHeaders
,
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();
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();
1046 Logger::Verbose("Response code was %d when proxying %s", code
, url
.c_str());
1050 ///////////////////////////////////////////////////////////////////////////////