text: /*
     +----------------------------------------------------------------------+
     | HipHop for PHP                                                       |
     +----------------------------------------------------------------------+
     | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com)  |
     +----------------------------------------------------------------------+
     | This source file is subject to version 3.01 of the PHP license,      |
     | that is bundled with this package in the file LICENSE, and is        |
     | available through the world-wide-web at the following url:           |
     | http://www.php.net/license/3_01.txt                                  |
     | If you did not receive a copy of the PHP license and are unable to   |
     | obtain it through the world-wide-web, please send a note to          |
     | license@php.net so we can mail you a copy immediately.               |
     +----------------------------------------------------------------------+
  */
  #include "hphp/runtime/server/http-protocol.h"
  
  #include <string>
  
  #include <folly/Conv.h>
  #include <folly/portability/SysTime.h>
  
  #include "hphp/util/arch.h"
  #include "hphp/util/logger.h"
  #include "hphp/util/stack-trace.h"
  #include "hphp/util/text-util.h"
  
  #include "hphp/runtime/base/array-init.h"
  #include "hphp/runtime/base/array-iterator.h"
  #include "hphp/runtime/base/builtin-functions.h"
  #include "hphp/runtime/base/http-client.h"
  #include "hphp/runtime/base/php-globals.h"
  #include "hphp/runtime/base/program-functions.h"
  #include "hphp/runtime/base/request-injection-data.h"
  #include "hphp/runtime/base/runtime-option.h"
  #include "hphp/runtime/base/string-util.h"
  #include "hphp/runtime/base/zend-string.h"
  #include "hphp/runtime/base/zend-url.h"
  #include "hphp/runtime/ext/string/ext_string.h"
  #include "hphp/runtime/server/cli-server.h"
  #include "hphp/runtime/server/replay-transport.h"
  #include "hphp/runtime/server/request-uri.h"
  #include "hphp/runtime/server/source-root-info.h"
  #include "hphp/runtime/server/transport.h"
  #include "hphp/runtime/server/upload.h"
  #include "hphp/runtime/server/virtual-host.h"
  #include "hphp/runtime/vm/globals-array.h"
  
  #define DEFAULT_POST_CONTENT_TYPE "application/x-www-form-urlencoded"
  
  namespace HPHP {
  ///////////////////////////////////////////////////////////////////////////////
  // helper functions
  
  static bool read_all_post_data(Transport *transport,
                                 const void *&data, size_t &size) {
    if (transport->hasMorePostData()) {
      data = buffer_duplicate(data, size);
      do {
        size_t delta = 0;
        const void *extra = transport->getMorePostData(delta);
        if (size + delta < VirtualHost::GetMaxPostSize()) {
          data = buffer_append(data, size, extra, delta);
          size += delta;
        }
      } while (transport->hasMorePostData());
      return true;
    }
    return false;
  }
  
  static void CopyParams(Array& dest, const Array& src) {
    IterateKVNoInc(
      src.get(),
      [&](Cell k, TypedValue v) {
        const auto arraykey =
          dest.convertKey<IntishCast::Cast>(k);
        dest.set(arraykey, v, true);
      }
    );
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  
  const VirtualHost *HttpProtocol::GetVirtualHost(Transport *transport) {
    if (!RuntimeOption::VirtualHosts.empty()) {
      std::string host = transport->getHeader("Host");
      if (auto vh = VirtualHost::Resolve(host)) {
        VirtualHost::SetCurrent(vh);
        return vh;
      }
    }
    VirtualHost::SetCurrent(nullptr);
    return VirtualHost::GetCurrent();
  }
  
  const StaticString
    s_REQUEST_START_TIME("REQUEST_START_TIME"),
    s_HPHP("HPHP"),
    s_HHVM("HHVM"),
    s_HHVM_JIT("HHVM_JIT"),
    s_HHVM_ARCH("HHVM_ARCH"),
    s_HPHP_SERVER("HPHP_SERVER"),
    s_HPHP_HOTPROFILER("HPHP_HOTPROFILER"),
    s_HTTP_HOST("HTTP_HOST"),
    s_CONTENT_TYPE("CONTENT_TYPE"),
    s_CONTENT_LENGTH("CONTENT_LENGTH"),
    s_PHP_AUTH_USER("PHP_AUTH_USER"),
    s_PHP_AUTH_PW("PHP_AUTH_PW"),
    s_PHP_AUTH_DIGEST("PHP_AUTH_DIGEST"),
    s_REQUEST_URI("REQUEST_URI"),
    s_SCRIPT_URL("SCRIPT_URL"),
    s_SCRIPT_URI("SCRIPT_URI"),
    s_SCRIPT_NAME("SCRIPT_NAME"),
    s_PHP_SELF("PHP_SELF"),
    s_SCRIPT_FILENAME("SCRIPT_FILENAME"),
    s_PATH_TRANSLATED("PATH_TRANSLATED"),
    s_PATH_INFO("PATH_INFO"),
    s_argc("argc"),
    s_argv("argv"),
    s__SERVER("_SERVER"),
    s__GET("_GET"),
    s__POST("_POST"),
    s__REQUEST("_REQUEST"),
    s__ENV("_ENV"),
    s__COOKIE("_COOKIE"),
    s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
    s__FILES("_FILES"),
    s_GATEWAY_INTERFACE("GATEWAY_INTERFACE"),
    s_CGI_1_1("CGI/1.1"),
    s_SERVER_ADDR("SERVER_ADDR"),
    s_SERVER_NAME("SERVER_NAME"),
    s_SERVER_PORT("SERVER_PORT"),
    s_SERVER_SOFTWARE("SERVER_SOFTWARE"),
    s_SERVER_PROTOCOL("SERVER_PROTOCOL"),
    s_SERVER_ADMIN("SERVER_ADMIN"),
    s_SERVER_SIGNATURE("SERVER_SIGNATURE"),
    s_REQUEST_METHOD("REQUEST_METHOD"),
    s_GET("GET"),
    s_HEAD("HEAD"),
    s_POST("POST"),
    s_HTTPS("HTTPS"),
    s_on("on"),
    s_REQUEST_TIME("REQUEST_TIME"),
    s_REQUEST_TIME_FLOAT("REQUEST_TIME_FLOAT"),
    s_QUERY_STRING("QUERY_STRING"),
    s_REMOTE_ADDR("REMOTE_ADDR"),
    s_REMOTE_HOST("REMOTE_HOST"),
    s_REMOTE_PORT("REMOTE_PORT"),
    s_DOCUMENT_ROOT("DOCUMENT_ROOT"),
    s_THREAD_TYPE("THREAD_TYPE"),
    s_dash("-"),
    s_underscore("_"),
    s_HTTP_("HTTP_"),
    s_forwardslash("/");
  
  static auto const s_arraysToClear = {
    s__SERVER,
    s__GET,
    s__POST,
    s__FILES,
    s__REQUEST,
    s__ENV,
    s__COOKIE,
  };
  
  static void PrepareEnv(Array& env, Transport *transport) {
    // $_ENV
    process_env_variables(env);
    env.set(s_HPHP, 1);
    env.set(s_HHVM, 1);
    if (RuntimeOption::EvalJit) {
      env.set(s_HHVM_JIT, 1);
    }
    switch (arch()) {
    case Arch::X64:
      env.set(s_HHVM_ARCH, "x64");
      break;
    case Arch::ARM:
      env.set(s_HHVM_ARCH, "arm");
      break;
    case Arch::PPC64:
      env.set(s_HHVM_ARCH, "ppc64");
      break;
    }
  
    bool isServer =
      RuntimeOption::ServerExecutionMode() && !is_cli_mode();
    if (isServer) {
      env.set(s_HPHP_SERVER, 1);
      env.set(s_HPHP_HOTPROFILER, 1);
    }
  
    // Do this last so it can overwrite all the previous settings
    HeaderMap transportParams;
    transport->getTransportParams(transportParams);
    for (auto const& header : transportParams) {
      String key(header.first);
      String value(header.second.back());
      g_context->setenv(key, value);
      env.set(env.convertKey<IntishCast::Cast>(key),
              make_tv<KindOfString>(value.get()));
    }
  }
  
  static void StartRequest(Array& server) {
    server.set(s_REQUEST_START_TIME, time(nullptr));
    time_t now;
    struct timeval tp = {0};
    double now_double;
    if (!gettimeofday(&tp, nullptr)) {
      now_double = (double)(tp.tv_sec + tp.tv_usec / 1000000.00);
      now = tp.tv_sec;
    } else {
      now = time(nullptr);
      now_double = (double)now;
    }
    server.set(s_REQUEST_TIME, now);
    server.set(s_REQUEST_TIME_FLOAT, now_double);
  }
  
  /**
   * PHP has "EGPCS" processing order of these global variables, and this
   * order is important in preparing $_REQUEST that needs to know which to
   * overwrite what when name happens to be the same.
   */
  void HttpProtocol::PrepareSystemVariables(Transport *transport,
                                            const RequestURI &r,
                                            const SourceRootInfo &sri) {
    auto const vhost = VirtualHost::GetCurrent();
    auto const& emptyArr = empty_darray();
    for (auto const& key : s_arraysToClear) {
      php_global_set(key, emptyArr);
    }
  
    // according to doc if content type is multipart/form-data
    // $HTTP_RAW_POST_DATA should always not available
    bool shouldSetHttpRawPostData = false;
    if (RuntimeOption::AlwaysPopulateRawPostData) {
      std::string dummy;
      if (!IsRfc1867(transport->getHeader("Content-Type"), dummy)) {
        shouldSetHttpRawPostData = true;
      }
    }
  
    if (shouldSetHttpRawPostData) {
      php_global_set(s_HTTP_RAW_POST_DATA, empty_string_variant_ref);
    }
  
  #define X(name)                                       \
    auto name##arr = empty_darray();                    \
    SCOPE_EXIT { php_global_set(s__##name, name##arr); };
  
    X(ENV)
    X(GET)
    X(POST)
    X(COOKIE)
    X(FILES)
    X(SERVER)
    X(REQUEST)
  
  #undef X
  
    Variant HTTP_RAW_POST_DATA;
    SCOPE_EXIT {
      if (shouldSetHttpRawPostData) {
        php_global_set(s_HTTP_RAW_POST_DATA, std::move(HTTP_RAW_POST_DATA));
  
      }
    };
  
    auto variablesOrder = RequestInfo::s_requestInfo.getNoCheck()
      ->m_reqInjectionData.getVariablesOrder();
  
    auto requestOrder = RequestInfo::s_requestInfo.getNoCheck()
      ->m_reqInjectionData.getRequestOrder();
  
    if (requestOrder.empty()) {
      requestOrder = variablesOrder;
    }
  
    bool postPopulated = false;
  
    for (char& c : variablesOrder) {
      switch(c) {
        case 'e':
        case 'E':
          PrepareEnv(ENVarr, transport);
          break;
        case 'g':
        case 'G':
          if (!r.queryString().empty()) {
            PrepareGetVariable(GETarr, r);
          }
          break;
        case 'p':
        case 'P':
          postPopulated = true;
          PreparePostVariables(POSTarr, HTTP_RAW_POST_DATA,
                               FILESarr, transport, r);
          break;
        case 'c':
        case 'C':
          PrepareCookieVariable(COOKIEarr, transport);
          break;
        case 's':
        case 'S':
          StartRequest(SERVERarr);
          PrepareServerVariable(SERVERarr,
              transport,
              r,
              sri,
              vhost);
          break;
      }
    }
  
    if (!postPopulated && shouldSetHttpRawPostData) {
      // Always try to populate $HTTP_RAW_POST_DATA if not populated
      auto dummyPost = empty_darray();
      auto dummyFiles = empty_darray();
      PreparePostVariables(dummyPost, HTTP_RAW_POST_DATA,
                           dummyFiles, transport, r);
    }
  
    PrepareRequestVariables(REQUESTarr,
                            GETarr,
                            POSTarr,
                            COOKIEarr,
                            requestOrder);
  }
  
  void HttpProtocol::PrepareRequestVariables(Array& request,
                                             const Array& get,
                                             const Array& post,
                                             const Array& cookie,
                                             const std::string& requestOrder) {
    for (const char& c : requestOrder) {
      switch(c) {
        case 'g':
        case 'G':
          CopyParams(request, get);
          break;
        case 'p':
        case 'P':
          CopyParams(request, post);
          break;
        case 'c':
        case 'C':
          CopyParams(request, cookie);
          break;
      }
    }
  
  }
  
  void HttpProtocol::PrepareGetVariable(Array& get,
                                        const RequestURI &r) {
    DecodeParameters(get,
                     r.queryString().data(),
                     r.queryString().size());
  }
  
  void HttpProtocol::PreparePostVariables(Array& post,
                                          Variant& raw_post,
                                          Array& files,
                                          Transport *transport,
                                          const RequestURI& r) {
    if (transport->getMethod() != Transport::Method::POST) {
      return;
    }
  
    std::string contentType = transport->getHeader("Content-Type");
    std::string contentLength = transport->getHeader("Content-Length");
  
    bool needDelete = false;
    size_t size = 0;
    const void *data = transport->getPostData(size);
    if (data && size) {
      std::string boundary;
      auto content_length = strtoll(contentLength.c_str(), nullptr, 10);
      bool rfc1867Post = IsRfc1867(contentType, boundary);
      std::string files_str;
      if (rfc1867Post) {
        if (content_length < 0 ||
            content_length > VirtualHost::GetMaxPostSize()) {
          // $_POST and $_FILES are empty
          Logger::Warning("POST Content-Length of %lld bytes exceeds "
                          "the limit of %" PRId64 " bytes",
                          content_length, VirtualHost::GetMaxPostSize());
          while (transport->hasMorePostData()) {
            size_t delta = 0;
            transport->getMorePostData(delta);
          }
          data = nullptr;
          size = 0;
        } else {
          // content_length is a reasonable nonnegative size.
          bool invalidate = false;
          if (transport->hasMorePostData()) {
            // Calls to getMorePostData may invalidate data, so make a copy
            // iff we're trying to coalesce the entire POST body.  Otherwise,
            // data may be invalid when DecodeRfc1867 returns.  See
            // upload.cpp:read_post.
            if (RuntimeOption::AlwaysPopulateRawPostData) {
              needDelete = true;
              data = buffer_duplicate(data, size);
            } else {
              invalidate = true;
            }
          }
          DecodeRfc1867(transport,
                        post,
                        files,
                        content_length,
                        data,
                        size,
                        boundary);
          if (invalidate) {
            data = nullptr;
            size = 0;
          }
        }
        assertx(!transport->getFiles(files_str));
      } else {
        needDelete = read_all_post_data(transport, data, size);
  
        bool decodeData = strncasecmp(contentType.c_str(),
                                      DEFAULT_POST_CONTENT_TYPE,
                                      sizeof(DEFAULT_POST_CONTENT_TYPE)-1) == 0;
  
        if (!decodeData) {
          auto vhost = VirtualHost::GetCurrent();
          if (vhost && vhost->alwaysDecodePostData(r.originalURL())) {
            decodeData = true;
          }
        }
  
        if (decodeData) {
          DecodeParameters(post, (const char*)data, size, true);
        }
  
        bool ret = transport->getFiles(files_str);
        if (ret) {
          files = unserialize_from_string(
            files_str,
            VariableUnserializer::Type::Serialize
          );
        }
      }
  
      if (!data) {
        return;
      }
      if (size > StringData::MaxSize) {
        // Can't store it anywhere
        if (needDelete) {
          free((void*) data);
        }
      } else {
        auto string_data = needDelete ?
          String((char*)data, size, AttachString) :
          String((char*)data, size, CopyString);
        g_context->setRawPostData(string_data);
        if (RuntimeOption::AlwaysPopulateRawPostData || ! needDelete) {
          // For literal we disregard RuntimeOption::AlwaysPopulateRawPostData
          raw_post = string_data;
        }
      }
    }
  }
  
  bool HttpProtocol::PrepareCookieVariable(Array& cookie,
                                           Transport *transport) {
  
    std::string cookie_data = transport->getHeader("Cookie");
    if (!cookie_data.empty()) {
      StringBuffer sb;
      sb.append(cookie_data);
      DecodeCookies(cookie, (char*)sb.data());
      return true;
    } else {
      return false;
    }
  }
  
  static void CopyHeaderVariables(Array& server,
                                  const HeaderMap& headers) {
    static std::atomic<int> badRequests(-1);
  
    std::vector<std::string> badHeaders;
    for (auto const& header : headers) {
      auto const& key = header.first;
      auto const& values = header.second;
      auto normalizedKey = s_HTTP_ +
                           string_replace(HHVM_FN(strtoupper)(key), s_dash,
                                          s_underscore);
  
      // Detect suspicious headers.  We are about to modify header names for
      // the SERVER variable.  This means that it is possible to deliberately
      // cause a header collision, which an attacker could use to sneak a
      // header past a proxy that would either overwrite or filter it
      // otherwise.  Client code should use apache_request_headers() to
      // retrieve the original headers if they are security-critical.
      if (RuntimeOption::LogHeaderMangle != 0 && server.exists(normalizedKey)) {
        badHeaders.push_back(key);
      }
  
      if (!values.empty()) {
        // When a header has multiple values, we always take the last one.
        server.set(normalizedKey, String(values.back()));
      }
    }
  
    if (!badHeaders.empty()) {
      auto reqId = badRequests.fetch_add(1, std::memory_order_acq_rel) + 1;
      if (!(reqId % RuntimeOption::LogHeaderMangle)) {
        std::string badNames = folly::join(", ", badHeaders);
        std::string allHeaders;
  
        const char* separator = "";
        for (auto const& header : headers) {
          for (auto const& value : header.second) {
            folly::toAppend(separator, header.first, ": ", value,
                            &allHeaders);
            separator = "\n";
          }
        }
  
        Logger::Warning(
          "HeaderMangle warning: "
          "The header(s) [%s] overwrote other headers which mapped to the same "
          "key. This happens because PHP normalises - to _, ie AN_EXAMPLE "
          "and AN-EXAMPLE are equivalent. You should treat this as "
          "malicious. All headers from this request:\n%s",
          badNames.c_str(), allHeaders.c_str());
      }
    }
  }
  
  static void CopyTransportParams(Array& server, Transport* transport) {
    HeaderMap transportParams;
    // Get additional server params from the transport if it has any. In the case
    // of fastcgi this is basically a full header list from apache/nginx.
    transport->getTransportParams(transportParams);
    for (auto const& header : transportParams) {
      // These overwrite anything already set in the $_SERVER
      // When a header has multiple values, we always take the last one.
      server.set(String(header.first), String(header.second.back()));
    }
  }
  
  static void CopyServerInfo(Array& server,
                             Transport *transport,
                             const VirtualHost *vhost) {
  
    std::string hostHeader = transport->getHeader("Host");
    String hostName(vhost->serverName(hostHeader));
    String serverNameHeader(transport->getServerName());
    if (hostHeader.empty()) {
      server.set(s_HTTP_HOST, hostName);
      StackTraceNoHeap::AddExtraLogging("Server", hostName.data());
    } else {
      // reset the HTTP_HOST header from apache.
      server.set(s_HTTP_HOST, hostHeader);
      StackTraceNoHeap::AddExtraLogging("Server", hostHeader.c_str());
    }
  
    // Use the header from the transport if it is available
    if (!serverNameHeader.empty()) {
      hostName = serverNameHeader;
    } else if (hostName.empty() || RuntimeOption::ForceServerNameToHeader) {
      hostName = hostHeader;
    }
  
    // _SERVER['SERVER_NAME'] shouldn't contain the port number
    int colonPos = hostName.find(':');
    if (colonPos != String::npos) {
      hostName = hostName.substr(0, colonPos);
    }
  
    StackTraceNoHeap::AddExtraLogging("Server_SERVER_NAME", hostName.data());
  
    server.set(s_GATEWAY_INTERFACE, s_CGI_1_1);
    server.set(s_SERVER_ADDR, transport->getServerAddr());
    server.set(s_SERVER_NAME, hostName);
    server.set(s_SERVER_PORT, transport->getServerPort());
    server.set(s_SERVER_SOFTWARE, transport->getServerSoftware());
    server.set(s_SERVER_PROTOCOL, "HTTP/" + transport->getHTTPVersion());
    server.set(s_SERVER_ADMIN, empty_string_variant_ref);
    server.set(s_SERVER_SIGNATURE, empty_string_variant_ref);
  }
  
  static void CopyRemoteInfo(Array& server, Transport *transport) {
    String remoteAddr(transport->getRemoteAddr(), CopyString);
    String remoteHost(transport->getRemoteHost(), CopyString);
    if (remoteAddr.empty()) {
      remoteAddr = remoteHost;
    }
    // Always set remoteAddr, even if it is empty
    server.set(s_REMOTE_ADDR, remoteAddr);
    if (!remoteHost.empty()) {
      server.set(s_REMOTE_HOST, remoteHost);
    }
    server.set(s_REMOTE_PORT, transport->getRemotePort());
  }
  
  static void CopyAuthInfo(Array& server, Transport *transport) {
    // APE processes Authorization: Basic into PHP_AUTH_USER and PHP_AUTH_PW
    std::string authorization = transport->getHeader("Authorization");
    if (!authorization.empty()) {
      if (strncmp(authorization.c_str(), "Basic ", 6) == 0) {
        // it's safe to pass this as a string literal since authorization
        // outlives decodedAuth; this saves us a superfluous copy.
        String decodedAuth =
          StringUtil::Base64Decode(String(authorization.c_str() + 6));
        int colonPos = decodedAuth.find(':');
        if (colonPos != String::npos) {
          server.set(s_PHP_AUTH_USER, decodedAuth.substr(0, colonPos));
          server.set(s_PHP_AUTH_PW, decodedAuth.substr(colonPos + 1));
        }
      } else if (strncmp(authorization.c_str(), "Digest ", 7) == 0) {
        server.set(s_PHP_AUTH_DIGEST, String(authorization.c_str() + 7));
      }
    }
  }
  
  static void CopyPathInfo(Array& server,
                           Transport *transport,
                           const RequestURI& r,
                           const VirtualHost *vhost) {
    server.set(s_REQUEST_URI, String(transport->getUrl(), CopyString));
    server.set(s_SCRIPT_URL, r.originalURL());
    String prefix(transport->isSSL() ? "https://" : "http://");
  
    // Need to append port
    assertx(server.exists(s_SERVER_PORT));
    std::string serverPort = "80";
    if (server.exists(s_SERVER_PORT)) {
      Variant port = server[s_SERVER_PORT];
      always_assert(port.isInteger() || port.isString());
      if (port.isInteger()) {
        serverPort = folly::to<std::string>(port.toInt32());
      } else {
        serverPort = port.toString().data();
      }
    }
  
    String port_suffix("");
    if (!transport->isSSL() && serverPort != "80") {
      port_suffix = folly::format(":{}", serverPort).str();
    }
  
    std::string hostHeader;
    if (server.exists(s_HTTP_HOST)) {
      hostHeader = server[s_HTTP_HOST].toCStrRef().data();
    }
    String hostName;
    if (server.exists(s_SERVER_NAME)) {
      assertx(server[s_SERVER_NAME].isString());
      hostName = server[s_SERVER_NAME].toCStrRef();
    }
    server.set(s_SCRIPT_URI,
               String(prefix + (hostHeader.empty() ? hostName + port_suffix :
                                String(hostHeader)) + r.originalURL()));
  
    if (r.rewritten()) {
      // when URL is rewritten, PHP decided to put original URL as SCRIPT_NAME
      String name = r.originalURL();
      if (!r.pathInfo().empty()) {
        int pos = name.find(r.pathInfo());
        if (pos >= 0) {
          name = name.substr(0, pos);
        }
      }
      if (r.defaultDoc()) {
        if (!name.empty() && name[name.length() - 1] != '/') {
          name += "/";
        }
        name += String(RuntimeOption::DefaultDocument);
      }
      server.set(s_SCRIPT_NAME, name);
    } else {
      server.set(s_SCRIPT_NAME, r.resolvedURL());
    }
  
    if (r.rewritten()) {
      server.set(s_PHP_SELF, r.originalURL());
    } else {
      server.set(s_PHP_SELF, r.resolvedURL() + r.origPathInfo());
    }
  
    String documentRoot = transport->getDocumentRoot();
    if (documentRoot.empty()) {
      // Right now this is just RuntimeOption::SourceRoot but mwilliams wants to
      // fix it so it is settable, so I'll leave this for now
      documentRoot = vhost->getDocumentRoot();
    }
    if (documentRoot != s_forwardslash &&
        documentRoot[documentRoot.length() - 1] == '/') {
      documentRoot = documentRoot.substr(0, documentRoot.length() - 1);
    }
    server.set(s_DOCUMENT_ROOT, documentRoot);
    server.set(s_SCRIPT_FILENAME, r.absolutePath());
  
    if (r.pathInfo().empty()) {
      server.set(s_PATH_TRANSLATED, r.absolutePath());
    } else {
      assertx(server.exists(s_DOCUMENT_ROOT));
      assertx(server[s_DOCUMENT_ROOT].isString());
      // reset path_translated back to the transport if it has it.
      auto const& pathTranslated = transport->getPathTranslated();
      if (!pathTranslated.empty()) {
        if (documentRoot == s_forwardslash) {
          // path outside document root or / is document root
          server.set(s_PATH_TRANSLATED, String(pathTranslated));
        } else {
          server.set(s_PATH_TRANSLATED,
                     String(server[s_DOCUMENT_ROOT].toCStrRef() +
                            s_forwardslash + pathTranslated));
        }
      } else {
        server.set(s_PATH_TRANSLATED,
                   String(server[s_DOCUMENT_ROOT].toCStrRef() +
                          server[s_SCRIPT_NAME].toCStrRef() +
                          r.pathInfo().data()));
      }
      server.set(s_PATH_INFO, r.pathInfo());
    }
  
    switch (transport->getMethod()) {
    case Transport::Method::GET:  server.set(s_REQUEST_METHOD, s_GET);  break;
    case Transport::Method::HEAD: server.set(s_REQUEST_METHOD, s_HEAD); break;
    case Transport::Method::POST:
      if (transport->getExtendedMethod() == nullptr) {
        server.set(s_REQUEST_METHOD, s_POST);
      } else {
        server.set(s_REQUEST_METHOD, transport->getExtendedMethod());
      }
      break;
    default:
      server.set(s_REQUEST_METHOD, empty_string_variant_ref); break;
    }
    server.set(s_HTTPS, transport->isSSL() ? Variant(s_on) :
                                             empty_string_variant_ref);
    server.set(s_QUERY_STRING, r.queryString());
  
    server.set(s_argv, make_varray(r.queryString()));
    server.set(s_argc, 1);
  }
  
  void HttpProtocol::PrepareServerVariable(Array& server,
                                           Transport *transport,
                                           const RequestURI &r,
                                           const SourceRootInfo &sri,
                                           const VirtualHost *vhost) {
    // $_SERVER
  
    std::string contentType = transport->getHeader("Content-Type");
    std::string contentLength = transport->getHeader("Content-Length");
  
    // HTTP_ headers -- we don't exclude headers we handle elsewhere (e.g.,
    // Content-Type, Authorization), since the CGI "spec" merely says the server
    // "may" exclude them; this is not what APE does, but it's harmless.
    auto const& headers = transport->getHeaders();
    // Do this first so other methods can overwrite them
    CopyHeaderVariables(server, headers);
    CopyServerInfo(server, transport, vhost);
    // Do this last so it can overwrite all the previous settings
    CopyTransportParams(server, transport);
    CopyRemoteInfo(server, transport);
    CopyAuthInfo(server, transport);
    CopyPathInfo(server, transport, r, vhost);
  
    // APE sets CONTENT_TYPE and CONTENT_LENGTH without HTTP_
    if (!contentType.empty()) {
      server.set(s_CONTENT_TYPE, String(contentType));
    }
    if (!contentLength.empty()) {
      server.set(s_CONTENT_LENGTH, String(contentLength));
    }
  
    for (auto& kv : RuntimeOption::ServerVariables) {
      String idx(kv.first);
      const auto arrkey =
        server.convertKey<IntishCast::Cast>(idx);
      String str(kv.second);
      server.set(arrkey, make_tv<KindOfString>(str.get()), true);
    }
    for (auto& kv : vhost->getServerVars()) {
      String idx(kv.first);
      const auto arrkey =
        server.convertKey<IntishCast::Cast>(idx);
      String str(kv.second);
      server.set(arrkey, make_tv<KindOfString>(str.get()), true);
    }
    server = sri.setServerVariables(std::move(server));
  
    const char *threadType = transport->getThreadTypeName();
    server.set(s_THREAD_TYPE, threadType);
    StackTraceNoHeap::AddExtraLogging("ThreadType", threadType);
  }
  
  std::string HttpProtocol::RecordRequest(Transport *transport) {
    char tmpfile[PATH_MAX + 1];
    if (RuntimeOption::RecordInput) {
      strcpy(tmpfile, "/tmp/hphp_request_XXXXXX");
      close(mkstemp(tmpfile));
  
      ReplayTransport rt;
      rt.recordInput(transport, tmpfile);
      Logger::Info("request recorded in %s", tmpfile);
      return tmpfile;
    }
    return "";
  }
  
  void HttpProtocol::ClearRecord(bool success, const std::string &tmpfile) {
    if (success && RuntimeOption::ClearInputOnSuccess && !tmpfile.empty()) {
      unlink(tmpfile.c_str());
      Logger::Info("request %s deleted", tmpfile.c_str());
    }
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  
  void HttpProtocol::DecodeParameters(Array& variables, const char *data,
                                      size_t size, bool post /* = false */) {
    if (data == nullptr || size == 0) {
      return;
    }
  
    const char *s = data;
    const char *e = s + size;
    const char *p, *val;
  
    while (s < e && (p = (const char *)memchr(s, '&', (e - s)))) {
    last_value:
      if ((val = (const char *)memchr(s, '=', (p - s)))) {
        String sname = url_decode(s, val - s);
  
        val++;
        String value = url_decode(val, p - val);
  
        register_variable(variables, (char*)sname.data(), value);
      } else if (!post) {
        String sname = url_decode(s, p - s);
        register_variable(variables, (char*)sname.data(),
                          empty_string_variant_ref);
      }
      s = p + 1;
    }
    if (s < e) {
      p = e;
      goto last_value;
    }
  }
  
  void HttpProtocol::DecodeCookies(Array& variables, char *data) {
    assertx(data && *data);
  
    char *strtok_buf = nullptr;
    char *var = strtok_r(data, ";", &strtok_buf);
    while (var) {
      char *val = strchr(var, '=');
  
      // Remove leading spaces from cookie names, needed for multi-cookie
      // header where ; can be followed by a space */
      while (isspace(*var)) {
        var++;
      }
  
      if (var != val && *var != '\0') {
        if (val) { /* have a value */
          String sname = url_decode(var, val - var);
  
          ++val;
          String value = url_decode(val, strlen(val));
  
          register_variable(variables, (char*)sname.data(), value, false);
        } else {
          String sname = url_decode(var, strlen(var));
  
          register_variable(variables, (char*)sname.data(),
                            empty_string_variant_ref, false);
        }
      }
  
      var = strtok_r(nullptr, ";", &strtok_buf);
    }
  }
  
  bool HttpProtocol::IsRfc1867(const std::string contentType, std::string &boundary) {
    if (contentType.empty()) return false;
    const char *ctstr = contentType.c_str();
    char *s;
    char *e;
    for (s = (char*)ctstr; *s && !(*s == ';' || *s == ',' || *s == ' '); s++) ;
    if (strncasecmp(ctstr, MULTIPART_CONTENT_TYPE, s - ctstr)) {
      return false;
    }
    s = strstr(s, "boundary");
    if (!s || !(s=strchr(s, '='))) {
      Logger::Warning("Missing boundary in multipart/form-data POST data");
      return false;
    }
    s++;
    if (s[0] == '"') {
      s++;
      e = strchr(s, '"');
      if (!e) {
        Logger::Warning("Invalid boundary in multipart/form-data POST data");
        return false;
      }
    } else {
      /* search for the end of the boundary */
      e = strpbrk(s, ",;");
    }
    if (e) {
      e[0] = '\0';
    }
    boundary = s;
    return true;
  }
  
  void HttpProtocol::DecodeRfc1867(Transport *transport,
                                   Array& post,
                                   Array& files,
                                   size_t contentLength,
                                   const void *&data,
                                   size_t &size,
                                   std::string boundary) {
    rfc1867PostHandler(transport,
                       post,
                       files,
                       contentLength,
                       data,
                       size,
                       boundary);
  }
  
  const char *HttpProtocol::GetReasonString(int code) {
    // https://tools.ietf.org/html/rfc7231#section-6.1
    switch (code) {
    case 100: return "Continue";
    case 101: return "Switching Protocols";
    case 200: return "OK";
    case 201: return "Created";
    case 202: return "Accepted";
    case 203: return "Non-Authoritative Information";
    case 204: return "No Content";
    case 205: return "Reset Content";
    case 206: return "Partial Content";
    case 300: return "Multiple Choices";
    case 301: return "Moved Permanently";
    case 302: return "Found";
    case 303: return "See Other";
    case 304: return "Not Modified";
    case 305: return "Use Proxy";
    case 307: return "Temporary Redirect";
    case 400: return "Bad Request";
    case 401: return "Unauthorized";
    case 402: return "Payment Required";
    case 403: return "Forbidden";
    case 404: return "Not Found";
    case 405: return "Method Not Allowed";
    case 406: return "Not Acceptable";
    case 407: return "Proxy Authentication Required";
    case 408: return "Request Timeout";
    case 409: return "Conflict";
    case 410: return "Gone";
    case 411: return "Length Required";
    case 412: return "Precondition Failed";
    case 413: return "Payload Too Large";
    case 414: return "URI Too Long";
    case 415: return "Unsupported Media Type";
    case 416: return "Range Not Satisfiable";
    case 417: return "Expectation Failed";
    case 500: return "Internal Server Error";
    case 501: return "Not Implemented";
    case 502: return "Bad Gateway";
    case 503: return "Service Unavailable";
    case 504: return "Gateway Timeout";
    case 505: return "HTTP Version Not Supported";
    }
    return "";
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  
  bool HttpProtocol::ProxyRequest(Transport *transport, bool force,
                                  const std::string &url,
                                  int &code, std::string &error,
                                  StringBuffer &response,
                                  HeaderMap *extraHeaders /* = NULL */) {
    assertx(transport);
    if (transport->headersSent()) {
      raise_warning("Cannot proxy request - headers already sent");
      return false;
    }
  
    auto requestHeaders = transport->getHeaders();
    if (extraHeaders) {
      for (HeaderMap::const_iterator iter = extraHeaders->begin();
           iter != extraHeaders->end(); ++iter) {
        std::vector<std::string> &values = requestHeaders[iter->first];
        values.insert(values.end(), iter->second.begin(), iter->second.end());
      }
    }
  
    size_t size = 0;
    const char *data = nullptr;
    if (transport->getMethod() == Transport::Method::POST) {
      data = (const char *)transport->getPostData(size);
    }
  
    req::vector<String> responseHeaders;
    HttpClient http;
    code = http.request(transport->getMethodName(),
                        url.c_str(), data, size, response, &requestHeaders,
                        &responseHeaders);
  
    if (code == 0) {
      if (!force) return false; // so we can retry
      Logger::Error("Unable to proxy %s: %s", url.c_str(),
                    http.getLastError().c_str());
      error = http.getLastError();
      return true;
    }
  
    for (unsigned int i = 0; i < responseHeaders.size(); i++) {
      String &header = responseHeaders[i];
      if (header.find(":") != String::npos &&
          header.find("Content-Length: ") != 0 &&
          header.find("Client-Transfer-Encoding: ") != 0 &&
          header.find("Transfer-Encoding: ") != 0 &&
          header.find("Connection: ") != 0) {
        transport->addHeader(header.data());
      }
    }
    const char* respData = response.data();
    if (!respData) {
      respData = "";
    }
    Logger::Verbose("Response code was %d when proxying %s", code, url.c_str());
    return true;
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  }
  