PageRenderTime 64ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/hphp/runtime/server/transport.cpp

http://github.com/facebook/hiphop-php
C++ | 991 lines | 764 code | 133 blank | 94 comment | 225 complexity | 81f3a6dd8711af71e2a1d504b9748155 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0
  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. +----------------------------------------------------------------------+
  15. */
  16. #include "hphp/runtime/server/transport.h"
  17. #include "hphp/runtime/server/server.h"
  18. #include "hphp/runtime/server/upload.h"
  19. #include "hphp/runtime/server/server-stats.h"
  20. #include "hphp/runtime/base/builtin-functions.h"
  21. #include "hphp/runtime/base/file.h"
  22. #include "hphp/runtime/base/string-util.h"
  23. #include "hphp/runtime/base/datetime.h"
  24. #include "hphp/runtime/base/runtime-option.h"
  25. #include "hphp/runtime/base/url.h"
  26. #include "hphp/runtime/base/zend-url.h"
  27. #include "hphp/runtime/base/tv-type.h"
  28. #include "hphp/runtime/server/access-log.h"
  29. #include "hphp/runtime/server/http-protocol.h"
  30. #include "hphp/runtime/server/compression.h"
  31. #include "hphp/runtime/ext/openssl/ext_openssl.h"
  32. #include "hphp/runtime/ext/string/ext_string.h"
  33. #include "hphp/util/compatibility.h"
  34. #include "hphp/util/hardware-counter.h"
  35. #include "hphp/util/logger.h"
  36. #include "hphp/util/service-data.h"
  37. #include "hphp/util/struct-log.h"
  38. #include "hphp/util/text-util.h"
  39. #include "hphp/util/timer.h"
  40. #include <folly/Random.h>
  41. #include <folly/String.h>
  42. namespace HPHP {
  43. ///////////////////////////////////////////////////////////////////////////////
  44. static const char HTTP_RESPONSE_STATS_PREFIX[] = "http_response_";
  45. Transport::Transport()
  46. : m_instructions(0), m_sleepTime(0), m_usleepTime(0),
  47. m_nsleepTimeS(0), m_nsleepTimeN(0), m_url(nullptr),
  48. m_postData(nullptr), m_postDataParsed(false),
  49. m_chunkedEncoding(false), m_headerSent(false),
  50. m_responseCode(-1), m_firstHeaderSet(false), m_firstHeaderLine(0),
  51. m_responseSize(0), m_responseTotalSize(0), m_responseSentSize(0),
  52. m_flushTimeUs(0), m_sendEnded(false), m_sendContentType(true),
  53. m_isSSL(false), m_threadType(ThreadType::RequestThread) {
  54. memset(&m_queueTime, 0, sizeof(m_queueTime));
  55. memset(&m_wallTime, 0, sizeof(m_wallTime));
  56. memset(&m_cpuTime, 0, sizeof(m_cpuTime));
  57. m_chunksSentSizes.clear();
  58. }
  59. Transport::~Transport() {
  60. if (m_url) {
  61. free(m_url);
  62. }
  63. if (m_postData) {
  64. free(m_postData);
  65. }
  66. m_chunksSentSizes.clear();
  67. }
  68. void Transport::onRequestStart(const timespec &queueTime) {
  69. m_queueTime = queueTime;
  70. Timer::GetMonotonicTime(m_wallTime);
  71. #ifdef CLOCK_THREAD_CPUTIME_ID
  72. gettime(CLOCK_THREAD_CPUTIME_ID, &m_cpuTime);
  73. #endif
  74. /*
  75. * The hardware counter is only 48 bits, so reset this at the beginning
  76. * of every request to make sure we don't overflow.
  77. */
  78. HardwareCounter::Reset();
  79. m_instructions = HardwareCounter::GetInstructionCount();
  80. auto const rate = RuntimeOption::EvalTraceServerRequestRate;
  81. if (rate && folly::Random::rand32(rate) == 0) forceInitRequestTrace();
  82. }
  83. const char *Transport::getMethodName() {
  84. switch (getMethod()) {
  85. case Method::GET: return "GET";
  86. case Method::HEAD: return "HEAD";
  87. case Method::POST: {
  88. const char *m = getExtendedMethod();
  89. return m ? m : "POST";
  90. }
  91. case Method::Unknown: {
  92. // m should really never be nullptr. "Unknown" is an actionable log line
  93. // since it indicates a missing HTTP method (since it is not capitalized).
  94. const char *m = getExtendedMethod();
  95. return m ? m : "Unknown";
  96. }
  97. default:
  98. break;
  99. }
  100. return "";
  101. }
  102. ///////////////////////////////////////////////////////////////////////////////
  103. // url
  104. const char *Transport::getServerObject() {
  105. const char *url = getUrl();
  106. return URL::getServerObject(url);
  107. }
  108. std::string Transport::getCommand() {
  109. const char *url = getServerObject();
  110. return URL::getCommand(url);
  111. }
  112. ///////////////////////////////////////////////////////////////////////////////
  113. // parameters
  114. // copied and re-factored from clearsilver-0.10.5/cgi/cgi.c
  115. void Transport::urlUnescape(char *value) {
  116. assertx(value && *value); // check before calling this function
  117. int i = 0, o = 0;
  118. unsigned char *s = (unsigned char *)value;
  119. while (s[i]) {
  120. if (s[i] == '+') {
  121. s[o++] = ' ';
  122. i++;
  123. } else if (s[i] == '%' && isxdigit(s[i+1]) && isxdigit(s[i+2])) {
  124. char num;
  125. num = (s[i+1] >= 'A') ? ((s[i+1] & 0xdf) - 'A') + 10 : (s[i+1] - '0');
  126. num *= 16;
  127. num += (s[i+2] >= 'A') ? ((s[i+2] & 0xdf) - 'A') + 10 : (s[i+2] - '0');
  128. s[o++] = num;
  129. i+=3;
  130. } else {
  131. s[o++] = s[i++];
  132. }
  133. }
  134. if (i && o) s[o] = '\0';
  135. }
  136. void Transport::parseQuery(char *query, ParamMap &params) {
  137. if (!query || !*query) return;
  138. char *l;
  139. char *k = strtok_r(query, "&", &l);
  140. while (k && *k) {
  141. char *v = strchr(k, '=');
  142. const char *fv;
  143. if (v == nullptr) {
  144. fv = "";
  145. } else {
  146. *v = '\0';
  147. v++;
  148. if (*v) urlUnescape(v);
  149. fv = v;
  150. }
  151. if (*k) urlUnescape(k);
  152. params[k].push_back(fv);
  153. k = strtok_r(nullptr, "&", &l);
  154. }
  155. }
  156. void Transport::parseGetParams() {
  157. if (m_url == nullptr) {
  158. const char *url = getServerObject();
  159. assertx(url);
  160. const char *p = strchr(url, '?');
  161. if (p) {
  162. m_url = strdup(p + 1);
  163. } else {
  164. m_url = strdup("");
  165. }
  166. parseQuery(m_url, m_getParams);
  167. }
  168. }
  169. void Transport::parsePostParams() {
  170. if (!m_postDataParsed) {
  171. assertx(m_postData == nullptr);
  172. size_t size;
  173. const char *data = (const char *)getPostData(size);
  174. if (data && *data && size) {
  175. // Post data may be binary, but if parsePostParams() is called, any
  176. // wellformed data cannot have embedded NULs. If it does, we simply
  177. // truncate it.
  178. m_postData = strndup(data, size);
  179. parseQuery(m_postData, m_postParams);
  180. }
  181. m_postDataParsed = true;
  182. }
  183. }
  184. bool Transport::paramExists(const char *name,
  185. Method method /* = Method::GET */) {
  186. assertx(name && *name);
  187. if (method == Method::GET || method == Method::AUTO) {
  188. if (m_url == nullptr) {
  189. parseGetParams();
  190. }
  191. if (m_getParams.find(name) != m_getParams.end()) {
  192. return true;
  193. }
  194. }
  195. if (method == Method::POST || method == Method::AUTO) {
  196. if (!m_postDataParsed) {
  197. parsePostParams();
  198. }
  199. if (m_postParams.find(name) != m_postParams.end()) {
  200. return true;
  201. }
  202. }
  203. return false;
  204. }
  205. std::string Transport::getParam(const char *name,
  206. Method method /* = Method::GET */) {
  207. assertx(name && *name);
  208. if (method == Method::GET || method == Method::AUTO) {
  209. if (m_url == nullptr) {
  210. parseGetParams();
  211. }
  212. ParamMap::const_iterator iter = m_getParams.find(name);
  213. if (iter != m_getParams.end()) {
  214. return iter->second[0];
  215. }
  216. }
  217. if (method == Method::POST || method == Method::AUTO) {
  218. if (!m_postDataParsed) {
  219. parsePostParams();
  220. }
  221. ParamMap::const_iterator iter = m_postParams.find(name);
  222. if (iter != m_postParams.end()) {
  223. return iter->second[0];
  224. }
  225. }
  226. return "";
  227. }
  228. int Transport::getIntParam(const char *name,
  229. Method method /* = Method::GET */) {
  230. std::string param = getParam(name, method);
  231. if (param.empty()) {
  232. return 0;
  233. }
  234. return atoi(param.c_str());
  235. }
  236. long long Transport::getInt64Param(const char *name,
  237. Method method /* = Method::GET */) {
  238. std::string param = getParam(name, method);
  239. if (param.empty()) {
  240. return 0;
  241. }
  242. return atoll(param.c_str());
  243. }
  244. void Transport::getArrayParam(const char *name,
  245. std::vector<std::string> &values,
  246. Method method /* = GET */) {
  247. if (method == Method::GET || method == Method::AUTO) {
  248. if (m_url == nullptr) {
  249. parseGetParams();
  250. }
  251. ParamMap::const_iterator iter = m_getParams.find(name);
  252. if (iter != m_getParams.end()) {
  253. const std::vector<const char *> &params = iter->second;
  254. values.insert(values.end(), params.begin(), params.end());
  255. }
  256. }
  257. if (method == Method::POST || method == Method::AUTO) {
  258. if (!m_postDataParsed) {
  259. parsePostParams();
  260. }
  261. ParamMap::const_iterator iter = m_postParams.find(name);
  262. if (iter != m_postParams.end()) {
  263. const std::vector<const char *> &params = iter->second;
  264. values.insert(values.end(), params.begin(), params.end());
  265. }
  266. }
  267. }
  268. void Transport::getSplitParam(const char *name,
  269. std::vector<std::string> &values,
  270. char delimiter,
  271. Method method /* = Method::GET */) {
  272. std::string param = getParam(name, method);
  273. if (!param.empty()) {
  274. folly::split(delimiter, param, values);
  275. }
  276. }
  277. ///////////////////////////////////////////////////////////////////////////////
  278. // headers
  279. bool Transport::splitHeader(const String& header, String &name, const char *&value) {
  280. int pos = header.find(':');
  281. if (pos != String::npos) {
  282. name = header.substr(0, pos);
  283. value = header.data() + pos;
  284. do {
  285. value++;
  286. } while (*value == ' ');
  287. return true;
  288. }
  289. // header("HTTP/1.0 404 Not Found");
  290. // header("HTTP/1.0 404");
  291. if (strncasecmp(header.data(), "http/", 5) == 0) {
  292. int pos1 = header.find(' ');
  293. if (pos1 != String::npos) {
  294. int pos2 = header.find(' ', pos1 + 1);
  295. if (pos2 == String::npos) pos2 = header.size();
  296. if (pos2 - pos1 > 1) {
  297. setResponse(atoi(header.data() + pos1),
  298. header.size() - pos2 > 1 ? header.data() + pos2 : nullptr);
  299. return false;
  300. }
  301. }
  302. }
  303. throw ExtendedException(
  304. "Invalid argument \"header\": [%s]", header.c_str());
  305. }
  306. void Transport::addHeaderNoLock(const char *name, const char *value) {
  307. assertx(name && *name);
  308. assertx(value);
  309. if (!m_firstHeaderSet) {
  310. m_firstHeaderSet = true;
  311. m_firstHeaderFile = g_context->getContainingFileName()->data();
  312. m_firstHeaderLine = g_context->getLine();
  313. }
  314. std::string svalue = value;
  315. replaceAll(svalue, "\n", "");
  316. m_responseHeaders[name].push_back(svalue);
  317. if (strcasecmp(name, "Location") == 0 && m_responseCode != 201 &&
  318. !(m_responseCode >= 300 && m_responseCode <=307)) {
  319. /* Zend seems to set 303 on a post with HTTP version > 1.0 in the code but
  320. * in our testing we can only get it to give 302.
  321. Method m = getMethod();
  322. if (m != Method::GET && m != Method::HEAD) {
  323. setResponse(303);
  324. } else {
  325. setResponse(302);
  326. }
  327. */
  328. setResponse(302);
  329. }
  330. }
  331. void Transport::addHeader(const char *name, const char *value) {
  332. assertx(name && *name);
  333. assertx(value);
  334. addHeaderNoLock(name, value);
  335. }
  336. void Transport::addHeader(const String& header) {
  337. String name;
  338. const char *value;
  339. if (splitHeader(header, name, value)) {
  340. addHeader(name.data(), value);
  341. }
  342. }
  343. void Transport::replaceHeader(const char *name, const char *value) {
  344. assertx(name && *name);
  345. assertx(value);
  346. m_responseHeaders[name].clear();
  347. addHeaderNoLock(name, value);
  348. }
  349. void Transport::replaceHeader(const String& header) {
  350. String name;
  351. const char *value;
  352. if (splitHeader(header, name, value)) {
  353. replaceHeader(name.data(), value);
  354. }
  355. }
  356. void Transport::removeHeader(const char *name) {
  357. if (name && *name) {
  358. m_responseHeaders.erase(name);
  359. if (strcasecmp(name, "Set-Cookie") == 0) {
  360. m_responseCookiesList.clear();
  361. }
  362. }
  363. }
  364. void Transport::removeAllHeaders() {
  365. m_responseHeaders.clear();
  366. m_responseCookiesList.clear();
  367. }
  368. void Transport::getResponseHeaders(HeaderMap &headers) {
  369. headers = m_responseHeaders;
  370. std::vector<std::string> &cookies = headers["Set-Cookie"];
  371. std::list<std::string> cookies_existing = getCookieLines();
  372. cookies.insert(cookies.end(), cookies_existing.begin(),
  373. cookies_existing.end());
  374. }
  375. void Transport::addToCommaSeparatedHeader(const char* name, const char* value) {
  376. assertx(name && *name);
  377. assertx(value);
  378. const auto it = m_responseHeaders.find(name);
  379. if (it != m_responseHeaders.end() && !it->second.empty()) {
  380. it->second[0] = it->second[0] + std::string(", ") + value;
  381. } else {
  382. addHeader(name, value);
  383. }
  384. }
  385. bool Transport::cookieExists(const char *name) {
  386. assertx(name && *name);
  387. std::string header = getHeader("Cookie");
  388. int len = strlen(name);
  389. bool hasValue = (strchr(name, '=') != nullptr);
  390. for (size_t pos = header.find(name); pos != std::string::npos;
  391. pos = header.find(name, pos + 1)) {
  392. if (pos == 0 || isspace(header[pos-1]) || header[pos-1] == ';') {
  393. pos += len;
  394. if (hasValue) {
  395. if (pos == header.size() || header[pos] == ';') return true;
  396. } else {
  397. if (pos < header.size() && header[pos] == '=') return true;
  398. }
  399. }
  400. }
  401. return false;
  402. }
  403. std::string Transport::getCookie(const std::string &name) {
  404. assertx(!name.empty());
  405. std::string header = getHeader("Cookie");
  406. for (size_t pos = header.find(name); pos != std::string::npos;
  407. pos = header.find(name, pos + 1)) {
  408. if (pos == 0 || isspace(header[pos-1]) || header[pos-1] == ';') {
  409. pos += name.size();
  410. if (pos < header.size() && header[pos] == '=') {
  411. size_t end = header.find(';', pos + 1);
  412. if (end != std::string::npos) end -= pos + 1;
  413. return header.substr(pos + 1, end);
  414. }
  415. }
  416. }
  417. return "";
  418. }
  419. bool Transport::acceptEncoding(const char *encoding) {
  420. return acceptsEncoding(this, encoding);
  421. }
  422. void Transport::setResponse(int code, const char *info) {
  423. m_responseCode = code;
  424. m_responseCodeInfo = info ? info : HttpProtocol::GetReasonString(code);
  425. }
  426. std::string Transport::getHTTPVersion() const {
  427. return "1.1";
  428. }
  429. size_t Transport::getRequestSize() const {
  430. return 0;
  431. }
  432. void Transport::setMimeType(const String& mimeType) {
  433. m_mimeType = mimeType.data();
  434. }
  435. String Transport::getMimeType() {
  436. return String(m_mimeType);
  437. }
  438. ///////////////////////////////////////////////////////////////////////////////
  439. // cookies
  440. namespace {
  441. // Make sure cookie names do not contain any illegal characters.
  442. // Throw a fatal exception if one does.
  443. void validateCookieNameString(const String& str) {
  444. if (!str.empty() && strpbrk(str.data(), "=,; \t\r\n\013\014")) {
  445. raise_error("Cookie names can not contain any of the following "
  446. "'=,; \\t\\r\\n\\013\\014'");
  447. }
  448. }
  449. // Make sure a component (path, value, domain) of a cookie does not
  450. // contain any illegal characters. Throw a fatal exception if it
  451. // does.
  452. void validateCookieString(const String& str, const char* component) {
  453. if (!str.empty() && strpbrk(str.data(), ",; \t\r\n\013\014")) {
  454. raise_error("Cookie %s can not contain any of the following "
  455. "',; \\t\\r\\n\\013\\014'", component);
  456. }
  457. }
  458. }
  459. bool Transport::setCookie(const String& name, const String& value, int64_t expire /* = 0 */,
  460. const String& path /* = "" */, const String& domain /* = "" */,
  461. bool secure /* = false */,
  462. bool httponly /* = false */,
  463. bool encode_url /* = true */) {
  464. validateCookieNameString(name);
  465. if (!encode_url) {
  466. validateCookieString(value, "values");
  467. }
  468. validateCookieString(path, "paths");
  469. validateCookieString(domain, "domains");
  470. String encoded_value;
  471. int len = 0;
  472. if (!value.empty()) {
  473. encoded_value = encode_url ? url_encode(value.data(), value.size())
  474. : value;
  475. len += encoded_value.size();
  476. }
  477. len += path.size();
  478. len += domain.size();
  479. std::string cookie;
  480. cookie.reserve(len + 100);
  481. if (value.empty()) {
  482. /*
  483. * MSIE doesn't delete a cookie when you set it to a null value
  484. * so in order to force cookies to be deleted, even on MSIE, we
  485. * pick an expiry date in the past
  486. */
  487. String sdt = req::make<DateTime>(1, true)->
  488. toString(DateTime::DateFormat::Cookie);
  489. cookie += name.data();
  490. cookie += "=deleted; expires=";
  491. cookie += sdt.data();
  492. cookie += "; Max-Age=0";
  493. } else {
  494. cookie += name.data();
  495. cookie += "=";
  496. cookie += encoded_value.isNull() ? "" : encoded_value.data();
  497. if (expire > 0) {
  498. if (expire > 253402300799LL) {
  499. raise_warning("Expiry date cannot have a year greater than 9999");
  500. return false;
  501. }
  502. cookie += "; expires=";
  503. String sdt = req::make<DateTime>(expire, true)->
  504. toString(DateTime::DateFormat::Cookie);
  505. cookie += sdt.data();
  506. cookie += "; Max-Age=";
  507. String sdelta = String(expire - time(0));
  508. cookie += sdelta.data();
  509. }
  510. }
  511. if (!path.empty()) {
  512. cookie += "; path=";
  513. cookie += path.data();
  514. }
  515. if (!domain.empty()) {
  516. cookie += "; domain=";
  517. cookie += domain.data();
  518. }
  519. if (secure) {
  520. cookie += "; secure";
  521. }
  522. if (httponly) {
  523. cookie += "; httponly";
  524. }
  525. // PHP5 does not deduplicate cookies. That behavior is preserved when
  526. // CookieDeduplicate is not enabled. Otherwise, we will only keep the
  527. // last cookie for a given name-domain-path triplet.
  528. String dedup_key = name + "\n" + domain + "\n" + path;
  529. m_responseCookiesList.emplace(m_responseCookiesList.end(),
  530. dedup_key.data(), cookie);
  531. return true;
  532. }
  533. std::list<std::string> Transport::getCookieLines() {
  534. std::list<std::string> ret;
  535. if (RuntimeOption::AllowDuplicateCookies) {
  536. for(CookieList::const_iterator iter = m_responseCookiesList.begin();
  537. iter != m_responseCookiesList.end(); ++iter) {
  538. ret.push_back(iter->second);
  539. }
  540. } else {
  541. // We will dedupe with last-one-wins semantics by walking backwards and
  542. // including only those whose dedupe key we have not seen yet, then
  543. // reversing the list
  544. std::unordered_set<std::string> already_seen;
  545. for(auto iter = m_responseCookiesList.crbegin();
  546. iter != m_responseCookiesList.crend(); ++iter) {
  547. if (already_seen.find(iter->first) == already_seen.end()) {
  548. ret.push_front(iter->second);
  549. already_seen.insert(iter->first);
  550. }
  551. }
  552. }
  553. return ret;
  554. }
  555. ///////////////////////////////////////////////////////////////////////////////
  556. void Transport::prepareHeaders(bool precompressed, bool chunked,
  557. const StringHolder &response, const StringHolder& orig_response) {
  558. for (HeaderMap::const_iterator iter = m_responseHeaders.begin();
  559. iter != m_responseHeaders.end(); ++iter) {
  560. const std::vector<std::string> &values = iter->second;
  561. for (unsigned int i = 0; i < values.size(); i++) {
  562. addHeaderImpl(iter->first.c_str(), values[i].c_str());
  563. }
  564. }
  565. const std::list<std::string> cookies = getCookieLines();
  566. for (std::list<std::string>::const_iterator iter = cookies.begin();
  567. iter != cookies.end(); ++iter) {
  568. addHeaderImpl("Set-Cookie", iter->c_str());
  569. }
  570. auto& compressor = getCompressor();
  571. // should never double-compress
  572. assertx(!precompressed || !compressor.isCompressed());
  573. if (precompressed) {
  574. // pre-compressed content is currently always gzip compressed.
  575. addHeaderImpl("Content-Encoding", "gzip");
  576. if (RuntimeOption::ServerAddVaryEncoding) {
  577. addHeaderImpl("Vary", "Accept-Encoding");
  578. }
  579. }
  580. if (precompressed || compressor.isCompressed()) {
  581. removeHeaderImpl("Content-Length");
  582. // Remove the Content-MD5 header coming from PHP if we compressed the data,
  583. // as the checksum is going to be invalid.
  584. auto it = m_responseHeaders.find("Content-MD5");
  585. if (it != m_responseHeaders.end()) {
  586. removeHeaderImpl("Content-MD5");
  587. // Re-add it back unless this is a chunked response. We'd have to buffer
  588. // the response completely to compute the MD5, which defeats the purpose
  589. // of chunking.
  590. if (chunked) {
  591. raise_warning("Cannot use chunked HTTP response and Content-MD5 header "
  592. "at the same time. Dropping Content-MD5.");
  593. } else {
  594. std::string cur_md5 = it->second[0];
  595. String expected_md5 = StringUtil::Base64Encode(StringUtil::MD5(
  596. orig_response.data(), orig_response.size(), true));
  597. // Can never trust these PHP people...
  598. if (expected_md5.c_str() != cur_md5) {
  599. raise_warning("Content-MD5 mismatch. Expected: %s, Got: %s",
  600. expected_md5.c_str(), cur_md5.c_str());
  601. }
  602. addHeaderImpl("Content-MD5", StringUtil::Base64Encode(StringUtil::MD5(
  603. response.data(), response.size(), true)).c_str());
  604. }
  605. }
  606. }
  607. if (m_responseHeaders.find("Content-Type") == m_responseHeaders.end() &&
  608. m_responseCode != 304) {
  609. std::string contentType = "text/html";
  610. if (IniSetting::Get("default_charset") != "") {
  611. contentType += "; charset=" + IniSetting::Get("default_charset");
  612. }
  613. addHeaderImpl("Content-Type", contentType.c_str());
  614. }
  615. if (RuntimeOption::ExposeHPHP) {
  616. addHeaderImpl("X-Powered-By", (String("HHVM/") + HHVM_VERSION).c_str());
  617. }
  618. if ((RuntimeOption::ExposeXFBServer || RuntimeOption::ExposeXFBDebug) &&
  619. !RuntimeOption::XFBDebugSSLKey.empty() &&
  620. m_responseHeaders.find("X-FB-Debug") == m_responseHeaders.end()) {
  621. String ip = this->getServerAddr();
  622. String key = RuntimeOption::XFBDebugSSLKey;
  623. String cipher("AES-256-CBC");
  624. bool crypto_strong = false;
  625. auto const iv_len = HHVM_FN(openssl_cipher_iv_length)(cipher).toInt32();
  626. auto const iv = HHVM_FN(openssl_random_pseudo_bytes)(
  627. iv_len, crypto_strong
  628. ).toString();
  629. auto const encrypted = HHVM_FN(openssl_encrypt)(
  630. ip, cipher, key, k_OPENSSL_RAW_DATA, iv
  631. ).toString();
  632. auto const output = StringUtil::Base64Encode(iv + encrypted);
  633. if (debug) {
  634. auto const decrypted = HHVM_FN(openssl_decrypt)(
  635. encrypted, cipher, key, k_OPENSSL_RAW_DATA, iv
  636. ).toString();
  637. assertx(decrypted.get()->same(ip.get()));
  638. }
  639. addHeaderImpl("X-FB-Debug", output.c_str());
  640. }
  641. // shutting down servers, so need to terminate all Keep-Alive connections
  642. if (!RuntimeOption::EnableKeepAlive || isServerStopping()) {
  643. addHeaderImpl("Connection", "close");
  644. removeHeaderImpl("Keep-Alive");
  645. // so lower level transports can ignore incoming "Connection: keep-alive"
  646. removeRequestHeaderImpl("Connection");
  647. }
  648. }
  649. namespace {
  650. void LogException(const char* msg) {
  651. try {
  652. throw;
  653. } catch (Exception& e) {
  654. Logger::Error("%s: %s", msg, e.getMessage().c_str());
  655. } catch (std::exception& e) {
  656. Logger::Error("%s: %s", msg, e.what());
  657. } catch (Object& e) {
  658. try {
  659. Logger::Error("%s: %s", msg, throwable_to_string(e.get()).c_str());
  660. } catch (...) {
  661. Logger::Error("%s: (e.toString() failed)", msg);
  662. }
  663. } catch (...) {
  664. Logger::Error("%s: (unknown exception)", msg);
  665. }
  666. }
  667. } // anonymous namespace
  668. StringHolder Transport::compressResponse(
  669. const char* data, int size, bool last) {
  670. StringHolder response(data, size, FreeType::NoFree);
  671. auto compressedResponse = getCompressor().compressResponse(data, size, last);
  672. if (compressedResponse.data() != nullptr) {
  673. response = std::move(compressedResponse);
  674. }
  675. return response;
  676. }
  677. ResponseCompressorManager& Transport::getCompressor() {
  678. if (!m_compressor) {
  679. m_compressor = std::make_unique<ResponseCompressorManager>(this);
  680. }
  681. return *m_compressor;
  682. }
  683. void Transport::enableCompression() {
  684. getCompressor().enable();
  685. }
  686. void Transport::disableCompression() {
  687. getCompressor().disable();
  688. }
  689. bool Transport::isCompressionEnabled() {
  690. return getCompressor().isEnabled();
  691. }
  692. void Transport::sendRaw(const char *data, int size, int code /* = 200 */,
  693. bool precompressed /* = false */,
  694. bool chunked /* = false */,
  695. const char *codeInfo /* = nullptr */
  696. ) {
  697. // There are post-send functions that can run. Any output from them should
  698. // be ignored as it doesn't make sense to try and send data after the
  699. // request has ended.
  700. if (m_sendEnded) {
  701. return;
  702. }
  703. if (!precompressed && RuntimeOption::ForceChunkedEncoding) {
  704. chunked = true;
  705. }
  706. // I don't think there is any need to send an empty chunk, other than sending
  707. // out headers earlier, which seems to be a useless feature.
  708. if (size == 0 && (chunked || m_chunkedEncoding)) {
  709. return;
  710. }
  711. if (chunked) {
  712. m_chunkedEncoding = true;
  713. }
  714. if (m_chunkedEncoding) {
  715. assertx(!precompressed);
  716. }
  717. sendRawInternal(data, size, code, precompressed, codeInfo);
  718. }
  719. void Transport::sendRawInternal(const char *data, int size,
  720. int code /* = 200 */,
  721. bool precompressed /* = false */,
  722. const char *codeInfo /* = nullptr */
  723. ) {
  724. bool chunked = m_chunkedEncoding;
  725. if (!g_context->m_headerCallbackDone &&
  726. !tvIsNull(&g_context->m_headerCallback)) {
  727. // We could use m_headerSent here, however it seems we can still
  728. // end up in an infinite loop when:
  729. // m_headerCallback calls flush()
  730. // flush() triggers php's recursion guard
  731. // the recursion guard calls back into m_headerCallback
  732. g_context->m_headerCallbackDone = true;
  733. try {
  734. vm_call_user_func(tvAsVariant(g_context->m_headerCallback),
  735. init_null_variant);
  736. } catch (...) {
  737. LogException("HeaderCallback");
  738. }
  739. }
  740. // compression handling
  741. ServerStatsHelper ssh("send");
  742. if (precompressed) {
  743. disableCompression();
  744. }
  745. StringHolder response = compressResponse(data, size, !chunked);
  746. if (m_responseCode < 0) {
  747. setResponse(code, codeInfo);
  748. }
  749. // HTTP header handling
  750. if (!m_headerSent) {
  751. prepareHeaders(precompressed, chunked, response,
  752. StringHolder(data, size, FreeType::NoFree));
  753. m_headerSent = true;
  754. }
  755. m_responseSize += response.size();
  756. ServerStats::SetThreadMode(ServerStats::ThreadMode::Writing);
  757. sendImpl(response.data(), response.size(), m_responseCode, chunked, false);
  758. ServerStats::SetThreadMode(ServerStats::ThreadMode::Processing);
  759. ServerStats::LogBytes(size);
  760. if (RuntimeOption::EnableStats && RuntimeOption::EnableWebStats) {
  761. ServerStats::Log("network.uncompressed", size);
  762. ServerStats::Log("network.compressed", response.size());
  763. }
  764. }
  765. void Transport::onSendEnd() {
  766. bool eomSent = false;
  767. if (m_chunkedEncoding) {
  768. assertx(m_headerSent);
  769. StringHolder response = compressResponse("", 0, true);
  770. sendImpl(response.data(), response.size(), m_responseCode, true, true);
  771. eomSent = true;
  772. } else if (!m_headerSent) {
  773. sendRawInternal("", 0);
  774. }
  775. auto httpResponseStats = ServiceData::createTimeSeries(
  776. folly::to<std::string>(HTTP_RESPONSE_STATS_PREFIX, getResponseCode()),
  777. {ServiceData::StatsType::SUM});
  778. httpResponseStats->addValue(1);
  779. if (!eomSent) {
  780. onSendEndImpl();
  781. }
  782. // Record that we have ended the request so any further output is discarded.
  783. m_sendEnded = true;
  784. }
  785. void Transport::redirect(const char *location, int code /* = 302 */,
  786. const char *info /* = nullptr */) {
  787. addHeaderImpl("Location", location);
  788. setResponse(code, info);
  789. sendString("Moved", code);
  790. }
  791. void Transport::onFlushProgress(int writtenSize, int64_t delayUs) {
  792. m_responseSentSize += writtenSize;
  793. m_flushTimeUs += delayUs;
  794. m_chunksSentSizes.push_back(writtenSize);
  795. }
  796. void Transport::onChunkedProgress(int writtenSize) {
  797. m_responseSentSize += writtenSize;
  798. m_chunksSentSizes.push_back(writtenSize);
  799. }
  800. void Transport::getChunkSentSizes(Array &ret) {
  801. for (unsigned int i = 0; i < m_chunksSentSizes.size(); i++) {
  802. ret.append(m_chunksSentSizes[i]);
  803. }
  804. }
  805. int Transport::getLastChunkSentSize() {
  806. size_t size = m_chunksSentSizes.size();
  807. return size == 0 ? 0 : m_chunksSentSizes.back();
  808. }
  809. ///////////////////////////////////////////////////////////////////////////////
  810. // support rfc1867
  811. bool Transport::isUploadedFile(const String& filename) {
  812. return is_uploaded_file(filename.c_str());
  813. }
  814. ///////////////////////////////////////////////////////////////////////////////
  815. // IDebuggable
  816. const char *Transport::getThreadTypeName() const {
  817. switch (m_threadType) {
  818. case ThreadType::RequestThread: return "Web Request";
  819. case ThreadType::PageletThread: return "Pagelet Thread";
  820. case ThreadType::XboxThread: return "Xbox Thread";
  821. case ThreadType::RpcThread: return "RPC Thread";
  822. }
  823. return "(unknown)";
  824. }
  825. void Transport::debuggerInfo(InfoVec &info) {
  826. Add(info, "Thread Type", getThreadTypeName());
  827. Add(info, "URL", getCommand());
  828. Add(info, "HTTP", getHTTPVersion());
  829. Add(info, "Method", getMethodName());
  830. if (getMethod() == Method::POST) {
  831. size_t size; getPostData(size);
  832. Add(info, "Post Data", FormatSize(size));
  833. }
  834. }
  835. ///////////////////////////////////////////////////////////////////////////////
  836. // StructuredLog
  837. StructuredLogEntry* Transport::createStructuredLogEntry() {
  838. assertx(!m_structLogEntry);
  839. m_structLogEntry = std::make_unique<StructuredLogEntry>();
  840. return m_structLogEntry.get();
  841. }
  842. StructuredLogEntry* Transport::getStructuredLogEntry() {
  843. return m_structLogEntry.get();
  844. }
  845. void Transport::resetStructuredLogEntry() {
  846. m_structLogEntry.reset();
  847. }
  848. ///////////////////////////////////////////////////////////////////////////////
  849. // Request tracing
  850. void Transport::forceInitRequestTrace() {
  851. if (m_requestTrace) return;
  852. m_requestTrace.emplace();
  853. rqtrace::ScopeGuard(
  854. getRequestTrace(), "REQUEST_QUEUE", getQueueTime()
  855. ).finish(getWallTime());
  856. }
  857. ///////////////////////////////////////////////////////////////////////////////
  858. }