2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/server/fastcgi/fastcgi-transport.h"
18 #include "hphp/runtime/server/fastcgi/fastcgi-server.h"
19 #include "hphp/runtime/server/http-protocol.h"
20 #include "hphp/runtime/server/transport.h"
21 #include "hphp/runtime/base/runtime-error.h"
22 #include "folly/io/IOBuf.h"
23 #include "folly/io/IOBufQueue.h"
24 #include "thrift/lib/cpp/async/TAsyncTransport.h" // @nolint
25 #include "thrift/lib/cpp/async/TAsyncTimeout.h" // @nolint
26 #include "folly/SocketAddress.h"
27 #include "hphp/util/logger.h"
28 #include "hphp/util/timer.h"
29 #include "folly/MoveWrapper.h"
31 #include <boost/algorithm/string/case_conv.hpp>
32 #include <boost/algorithm/string/predicate.hpp>
35 using folly::IOBufQueue
;
36 using folly::io::Cursor
;
37 using folly::io::QueueAppender
;
41 ///////////////////////////////////////////////////////////////////////////////
43 FastCGITransport::FastCGITransport(FastCGIConnection
* connection
, int id
)
44 : m_connection(connection
),
48 m_method(Method::Unknown
),
53 m_readComplete(false) {}
55 const char *FastCGITransport::getUrl() {
56 return m_requestURI
.c_str();
59 const std::string
FastCGITransport::getScriptFilename() {
60 return m_scriptFilename
;
63 const std::string
FastCGITransport::getPathTranslated() {
64 return m_pathTranslated
;
67 const std::string
FastCGITransport::getPathInfo() {
71 bool FastCGITransport::isPathInfoSet() {
75 const std::string
FastCGITransport::getDocumentRoot() {
76 return m_documentRoot
;
79 const char *FastCGITransport::getRemoteHost() {
80 return m_remoteHost
.c_str();
83 const char *FastCGITransport::getRemoteAddr() {
84 return m_remoteAddr
.c_str();
87 uint16_t FastCGITransport::getRemotePort() {
91 const char *FastCGITransport::getServerName() {
92 return m_serverName
.c_str();
95 const char *FastCGITransport::getServerAddr() {
96 return (!m_serverAddr
.empty()) ? m_serverAddr
.c_str() :
97 Transport::getServerAddr();
100 uint16_t FastCGITransport::getServerPort() {
101 return (m_serverPort
!= 0) ? m_serverPort
: Transport::getServerPort();
104 const char *FastCGITransport::getServerSoftware() {
105 return (!m_serverSoftware
.empty()) ? m_serverSoftware
.c_str() :
106 Transport::getServerSoftware();
109 const void *FastCGITransport::getPostData(int &size
) {
111 return getPostDataImpl(size
, false);
114 const void *FastCGITransport::getMorePostData(int &size
) {
116 return getPostDataImpl(size
, true);
119 bool FastCGITransport::hasMorePostData() {
121 bool result
= !m_readComplete
|| !m_bodyQueue
.empty();
126 const void *FastCGITransport::getPostDataImpl(int &size
, bool progress
) {
127 const void* result
= nullptr;
129 if (!progress
&& m_currBody
!= nullptr) {
130 size
= m_currBody
->length();
131 return m_currBody
->data();
135 if (m_bodyQueue
.empty() && m_readComplete
) {
138 while (!m_bodyQueue
.empty()) {
139 size
= m_bodyQueue
.front()->length();
141 m_currBody
= m_bodyQueue
.pop_front();
142 result
= m_currBody
->data();
145 m_bodyQueue
.pop_front();
148 while (size
== 0 && m_bodyQueue
.empty() && !m_readComplete
) {
161 Transport::Method
FastCGITransport::getMethod() {
165 const char *FastCGITransport::getExtendedMethod() {
166 return m_extendedMethod
.c_str();
169 std::string
FastCGITransport::getHTTPVersion() const {
170 return m_httpVersion
;
173 int FastCGITransport::getRequestSize() const {
174 return m_requestSize
;
177 const char *FastCGITransport::getServerObject() {
178 return m_serverObject
.c_str();
181 std::string
FastCGITransport::unmangleHeader(const std::string
& name
) {
182 if (name
== "Authorization") {
183 return name
; // Already unmangled
186 if (name
== "CONTENT_LENGTH") {
187 return "Content-Length";
190 if (name
== "CONTENT_TYPE") {
191 return "Content-Type";
194 if (!boost::istarts_with(name
, "HTTP_")) {
199 bool is_upper
= true;
200 for (auto& c
: name
.substr(5)) {
205 ret
+= is_upper
? toupper(c
) : tolower(c
);
212 std::string
FastCGITransport::mangleHeader(const std::string
& name
) {
214 for (auto& c
: name
) {
221 return "HTTP_" + ret
;
224 static const std::string
225 s_authorization("Authorization"),
226 s_contentLength("CONTENT_LENGTH"),
227 s_contentType("CONTENT_TYPE");
230 * Passed an HTTP header like "Cookie" or "Cache-Control"
232 std::string
FastCGITransport::getHeader(const char *name
) {
233 auto *header
= getRawHeaderPtr(mangleHeader(name
));
237 if (strcasecmp(name
, "Authorization") == 0) {
238 return getRawHeader(s_authorization
); // No HTTP_ prefix for Authorization
240 if (strcasecmp(name
, "Content-Length") == 0) {
241 return getRawHeader(s_contentLength
); // No HTTP_ prefix for CONTENT_LENGTH
243 if (strcasecmp(name
, "Content-Type") == 0) {
244 return getRawHeader(s_contentType
); // No HTTP_ prefix for CONTENT_TYPE
250 * Passed a FastCGI mangled header like "HTTP_COOKIE" or "HTTP_CACHE_CONTROL"
252 std::string
FastCGITransport::getRawHeader(const std::string
& name
) {
253 auto header
= getRawHeaderPtr(name
);
254 return (header
== nullptr) ? std::string
{""} : *header
;
257 std::string
* FastCGITransport::getRawHeaderPtr(const std::string
& name
) {
258 auto it
= m_requestHeaders
.find(name
);
259 return (it
== m_requestHeaders
.end()) ? nullptr : &it
->second
;
262 int FastCGITransport::getIntHeader(const std::string
& name
) {
264 auto* key
= getRawHeaderPtr(name
);
265 return (key
!= nullptr && !key
->empty()) ? std::stoi(*key
) : 0;
266 } catch (std::invalid_argument
&) {
268 } catch (std::out_of_range
&) {
273 void FastCGITransport::getHeaders(HeaderMap
&headers
) {
274 for (auto& pair
: m_requestHeaders
) {
275 auto key
= unmangleHeader(pair
.first
);
277 headers
[key
] = { pair
.second
};
282 void FastCGITransport::getTransportParams(HeaderMap
&serverParams
) {
283 for (auto& pair
: m_requestHeaders
) {
284 serverParams
[pair
.first
] = { pair
.second
};
288 void FastCGITransport::addHeaderImpl(const char* name
, const char* value
) {
289 CHECK(!m_headersSent
);
290 auto it
= m_responseHeaders
.find(name
);
291 if (it
== m_responseHeaders
.end()) {
292 m_responseHeaders
.emplace(name
, std::vector
<std::string
>{value
});
294 it
->second
.emplace_back(value
);
298 void FastCGITransport::removeHeaderImpl(const char* name
) {
299 CHECK(!m_headersSent
);
301 m_responseHeaders
.erase(name
);
304 static const std::string
305 s_status("Status: "),
310 void FastCGITransport::sendResponseHeaders(IOBufQueue
& queue
, int code
) {
311 CHECK(!m_headersSent
);
312 m_headersSent
= true;
315 queue
.append(s_status
);
316 queue
.append(std::to_string(code
));
317 auto reasonStr
= getResponseInfo();
318 if (reasonStr
.empty()) {
319 reasonStr
= HttpProtocol::GetReasonString(code
);
321 queue
.append(s_space
);
322 queue
.append(reasonStr
);
323 queue
.append(s_newline
);
326 for (auto& header
: m_responseHeaders
) {
327 for (auto& value
: header
.second
) {
328 queue
.append(header
.first
);
329 queue
.append(s_colon
);
331 queue
.append(s_newline
);
334 queue
.append(s_newline
);
337 void FastCGITransport::sendImpl(const void *data
, int size
, int code
,
340 if (!m_headersSent
) {
341 sendResponseHeaders(queue
, code
);
343 queue
.append(IOBuf::copyBuffer(data
, size
));
344 folly::MoveWrapper
<std::unique_ptr
<IOBuf
>> chain_wrapper(queue
.move());
345 Callback
* callback
= m_callback
;
346 auto fn
= [callback
, chain_wrapper
]() mutable {
348 callback
->onStdOut(std::move(*chain_wrapper
));
351 m_connection
->getEventBase()->runInEventBaseThread(fn
);
354 void FastCGITransport::onSendEndImpl() {
355 Callback
* callback
= m_callback
;
356 auto fn
= [callback
]() mutable {
358 callback
->onComplete();
361 m_connection
->getEventBase()->runInEventBaseThread(fn
);
364 void FastCGITransport::onBody(std::unique_ptr
<folly::IOBuf
> chain
) {
365 Cursor
cursor(chain
.get());
366 size_t length
= chain
->computeChainDataLength();
367 std::string s
= cursor
.readFixedString(length
);
369 m_bodyQueue
.append(s
);
376 void FastCGITransport::onBodyComplete() {
378 m_readComplete
= true;
385 void FastCGITransport::onHeader(std::unique_ptr
<folly::IOBuf
> key_chain
,
386 std::unique_ptr
<folly::IOBuf
> value_chain
) {
387 Cursor
cursor(key_chain
.get());
388 std::string key
= cursor
.readFixedString(key_chain
->computeChainDataLength());
389 cursor
= Cursor(value_chain
.get());
390 std::string value
= cursor
.readFixedString(
391 value_chain
->computeChainDataLength());
392 auto it
= m_requestHeaders
.emplace(key
, value
);
394 it
.first
->second
= value
;
398 static const std::string
399 s_requestURI("REQUEST_URI"),
400 s_remoteHost("REMOTE_HOST"),
401 s_remoteAddr("REMOTE_ADDR"),
402 s_serverName("SERVER_NAME"),
403 s_serverAddr("SERVER_ADDR"),
404 s_serverSoftware("SERVER_SOFTWARE"),
405 s_extendedMethod("REQUEST_METHOD"),
406 s_httpVersion("HTTP_VERSION"),
407 s_documentRoot("DOCUMENT_ROOT"),
408 s_remotePort("REMOTE_PORT"),
409 s_serverPort("SERVER_PORT"),
410 s_pathTranslated("PATH_TRANSLATED"),
411 s_scriptName("SCRIPT_NAME"),
412 s_scriptFilename("SCRIPT_FILENAME"),
413 s_queryString("QUERY_STRING"),
415 s_pathInfo("PATH_INFO"),
417 s_modProxy("proxy:"),
418 s_modProxySearch("://"),
421 void FastCGITransport::onHeadersComplete() {
422 m_requestURI
= getRawHeader(s_requestURI
);
423 m_remoteHost
= getRawHeader(s_remoteHost
);
424 m_remoteAddr
= getRawHeader(s_remoteAddr
);
425 m_serverName
= getRawHeader(s_serverName
);
426 m_serverAddr
= getRawHeader(s_serverAddr
);
427 m_serverSoftware
= getRawHeader(s_serverSoftware
);
428 m_extendedMethod
= getRawHeader(s_extendedMethod
);
429 m_httpVersion
= getRawHeader(s_httpVersion
);
430 m_serverObject
= getRawHeader(s_scriptName
);
431 m_scriptFilename
= getRawHeader(s_scriptFilename
);
432 m_pathTranslated
= getRawHeader(s_pathTranslated
);
433 if (getRawHeaderPtr(s_pathInfo
) != nullptr) {
434 m_pathInfoSet
= true;
436 m_pathInfo
= getRawHeader(s_pathInfo
);
437 m_documentRoot
= getRawHeader(s_documentRoot
);
438 if (!m_documentRoot
.empty() &&
439 m_documentRoot
[m_documentRoot
.length() - 1] != '/') {
440 m_documentRoot
+= '/';
443 m_serverPort
= getIntHeader(s_serverPort
);
444 m_requestSize
= getIntHeader(s_contentLength
);
445 int port
= getIntHeader(s_remotePort
);
446 if (port
< std::numeric_limits
<decltype(m_remotePort
)>::min() ||
447 port
> std::numeric_limits
<decltype(m_remotePort
)>::max()) {
452 auto* value
= getRawHeaderPtr(s_https
);
453 if (value
!= nullptr && !value
->empty()) {
454 auto lValue
= std::string
{*value
};
455 for (auto& c
: lValue
) {
458 // IIS sets this value but sets it to off when SSL is off.
459 if (lValue
!= "OFF") {
464 // Treat everything apart from GET and HEAD as a post to be like php-src.
465 if (m_extendedMethod
== "GET") {
466 m_method
= Method::GET
;
467 } else if (m_extendedMethod
== "HEAD") {
468 m_method
= Method::HEAD
;
470 m_method
= Method::POST
;
473 if (m_httpVersion
.empty()) {
474 // If we didn't receive a version, assume default transport version.
475 // Flushing the request early requires HTTP_VERSION to be 1.1.
476 m_httpVersion
= Transport::getHTTPVersion();
479 if (m_scriptFilename
.empty() || RuntimeOption::ServerFixPathInfo
) {
480 // According to php-fpm, some servers don't set SCRIPT_FILENAME. In
481 // this case, it uses PATH_TRANSLATED.
482 // Added runtime option to change m_scriptFilename to s_pathTranslated
483 // which will allow mod_fastcgi and mod_action to work correctly.
484 m_scriptFilename
= getRawHeader(s_pathTranslated
);
487 // do a check for mod_proxy_fcgi and remove the extra portions of the string
488 if (m_scriptFilename
.find(s_modProxy
) == 0) {
489 // remove the proxy:type + :// from the start.
490 int proxyPos
= m_scriptFilename
.find(s_modProxySearch
);
491 if (proxyPos
!= String::npos
) {
492 m_scriptFilename
= m_scriptFilename
.substr(proxyPos
+ s_modProxySearch
.size());
494 // remove everything before the first / which is host:port
495 int slashPos
= m_scriptFilename
.find(s_slash
);
496 if (slashPos
!= String::npos
) {
497 m_scriptFilename
= m_scriptFilename
.substr(slashPos
);
499 // remove everything after the first ?
500 int questionPos
= m_scriptFilename
.find(s_questionMark
);
501 if (questionPos
!= String::npos
) {
502 m_scriptFilename
= m_scriptFilename
.substr(0, questionPos
);
506 // RequestURI needs script_filename and path_translated to not include
508 if (!m_pathTranslated
.empty()) {
509 if (m_pathTranslated
.find(m_documentRoot
) == 0) {
510 m_pathTranslated
= m_pathTranslated
.substr(m_documentRoot
.length());
514 if (!m_scriptFilename
.empty()) {
515 if (m_scriptFilename
.find(m_documentRoot
) == 0) {
516 m_scriptFilename
= m_scriptFilename
.substr(m_documentRoot
.length());
518 // if the document root isn't in the url set document root to /
519 m_documentRoot
= "/";
523 auto* queryString
= getRawHeaderPtr(s_queryString
);
524 if (queryString
!= nullptr && !queryString
->empty()) {
525 m_serverObject
+= "?" + *queryString
;
528 m_connection
->handleRequest(m_id
);
531 ///////////////////////////////////////////////////////////////////////////////