Support Authorization header in apache_request_headers()
[hiphop-php.git] / hphp / runtime / server / fastcgi / fastcgi-transport.cpp
blob83111403ebbc1a0bb26da1241823bc6884d41c37
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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>
34 using folly::IOBuf;
35 using folly::IOBufQueue;
36 using folly::io::Cursor;
37 using folly::io::QueueAppender;
39 namespace HPHP {
41 ///////////////////////////////////////////////////////////////////////////////
43 FastCGITransport::FastCGITransport(FastCGIConnection* connection, int id)
44 : m_connection(connection),
45 m_id(id),
46 m_remotePort(0),
47 m_serverPort(0),
48 m_method(Method::Unknown),
49 m_requestSize(0),
50 m_headersSent(false),
51 m_readMore(false),
52 m_waiting(0),
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() {
68 return m_pathInfo;
71 bool FastCGITransport::isPathInfoSet() {
72 return m_pathInfoSet;
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() {
88 return m_remotePort;
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) {
110 assert(!m_readMore);
111 return getPostDataImpl(size, false);
114 const void *FastCGITransport::getMorePostData(int &size) {
115 m_readMore = true;
116 return getPostDataImpl(size, true);
119 bool FastCGITransport::hasMorePostData() {
120 m_monitor.lock();
121 bool result = !m_readComplete || !m_bodyQueue.empty();
122 m_monitor.unlock();
123 return result;
126 const void *FastCGITransport::getPostDataImpl(int &size, bool progress) {
127 const void* result = nullptr;
128 size = 0;
129 if (!progress && m_currBody != nullptr) {
130 size = m_currBody->length();
131 return m_currBody->data();
133 m_monitor.lock();
134 while (size == 0) {
135 if (m_bodyQueue.empty() && m_readComplete) {
136 break;
138 while (!m_bodyQueue.empty()) {
139 size = m_bodyQueue.front()->length();
140 if (size != 0) {
141 m_currBody = m_bodyQueue.pop_front();
142 result = m_currBody->data();
143 break;
144 } else {
145 m_bodyQueue.pop_front();
148 while (size == 0 && m_bodyQueue.empty() && !m_readComplete) {
149 m_waiting += 1;
150 m_monitor.wait();
151 m_waiting -= 1;
154 if (m_waiting > 0) {
155 m_monitor.notify();
157 m_monitor.unlock();
158 return result;
161 Transport::Method FastCGITransport::getMethod() {
162 return m_method;
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_")) {
195 return "";
198 std::string ret;
199 bool is_upper = true;
200 for (auto& c : name.substr(5)) {
201 if (c == '_') {
202 ret += '-';
203 is_upper = true;
204 } else {
205 ret += is_upper ? toupper(c) : tolower(c);
206 is_upper = false;
209 return ret;
212 std::string FastCGITransport::mangleHeader(const std::string& name) {
213 std::string ret;
214 for (auto& c : name) {
215 if (c == '-') {
216 ret += '_';
217 } else {
218 ret += toupper(c);
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));
234 if (header) {
235 return *header;
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
246 return "";
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) {
263 try {
264 auto* key = getRawHeaderPtr(name);
265 return (key != nullptr && !key->empty()) ? std::stoi(*key) : 0;
266 } catch (std::invalid_argument&) {
267 return 0;
268 } catch (std::out_of_range&) {
269 return 0;
273 void FastCGITransport::getHeaders(HeaderMap &headers) {
274 for (auto& pair : m_requestHeaders) {
275 auto key = unmangleHeader(pair.first);
276 if (!key.empty()) {
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});
293 } else {
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: "),
306 s_space(" "),
307 s_colon(": "),
308 s_newline("\r\n");
310 void FastCGITransport::sendResponseHeaders(IOBufQueue& queue, int code) {
311 CHECK(!m_headersSent);
312 m_headersSent = true;
314 if (code != 200) {
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);
330 queue.append(value);
331 queue.append(s_newline);
334 queue.append(s_newline);
337 void FastCGITransport::sendImpl(const void *data, int size, int code,
338 bool chunked) {
339 IOBufQueue queue;
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 {
347 if (callback) {
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 {
357 if (callback) {
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);
368 m_monitor.lock();
369 m_bodyQueue.append(s);
370 if (m_waiting > 0) {
371 m_monitor.notify();
373 m_monitor.unlock();
376 void FastCGITransport::onBodyComplete() {
377 m_monitor.lock();
378 m_readComplete = true;
379 if (m_waiting > 0) {
380 m_monitor.notify();
382 m_monitor.unlock();
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);
393 if (!it.second) {
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"),
414 s_https("HTTPS"),
415 s_pathInfo("PATH_INFO"),
416 s_slash("/"),
417 s_modProxy("proxy:"),
418 s_modProxySearch("://"),
419 s_questionMark("?");
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()) {
448 port = 0;
450 m_remotePort = port;
452 auto* value = getRawHeaderPtr(s_https);
453 if (value != nullptr && !value->empty()) {
454 auto lValue = std::string{*value};
455 for (auto& c : lValue) {
456 c = std::toupper(c);
458 // IIS sets this value but sets it to off when SSL is off.
459 if (lValue != "OFF") {
460 setSSL();
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;
469 } else {
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
507 // the document root
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());
517 } else {
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 ///////////////////////////////////////////////////////////////////////////////