void HttpProtocol::PrepareRequestVariables(Array& request, Array& get, Array& post, Variant& raw_post, Array& files, Array& cookie, Transport *transport, const RequestURI &r) { // $_GET and $_REQUEST if (!r.queryString().empty()) { PrepareGetVariable(get, r); CopyParams(request, get); } // $_POST and $_REQUEST if (transport->getMethod() == Transport::Method::POST) { PreparePostVariables(post, raw_post, files, transport); CopyParams(request, post); } // $_COOKIE if (PrepareCookieVariable(cookie, transport)) { CopyParams(request, cookie); } }
/** * 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) { SystemGlobals *g = (SystemGlobals*)get_global_variables(); const VirtualHost *vhost = VirtualHost::GetCurrent(); // reset global symbols to nulls or empty arrays pm_php$globals$symbols_php(); Variant &server = g->GV(_SERVER); server.set("REQUEST_START_TIME", time(NULL)); // $_ENV process_env_variables(g->GV(_ENV)); g->GV(_ENV).set("HPHP", 1); g->GV(_ENV).set("HPHP_SERVER", 1); #ifdef HOTPROFILER g->GV(_ENV).set("HPHP_HOTPROFILER", 1); #endif Variant &request = g->GV(_REQUEST); // $_GET and $_REQUEST if (!r.queryString().empty()) { DecodeParameters(g->GV(_GET), r.queryString().data(), r.queryString().size()); CopyParams(request, g->GV(_GET)); } string contentType = transport->getHeader("Content-Type"); string contentLength = transport->getHeader("Content-Length"); // $_POST and $_REQUEST if (transport->getMethod() == Transport::POST) { bool needDelete = false; int size = 0; const void *data = transport->getPostData(size); if (data && size) { ASSERT(((char*)data)[size] == 0); // we need a NULL terminated string string boundary; int content_length = atoi(contentLength.c_str()); bool rfc1867Post = IsRfc1867(contentType, boundary); string files; if (rfc1867Post) { if (content_length > VirtualHost::GetMaxPostSize()) { // $_POST and $_FILES are empty Logger::Warning("POST Content-Length of %d bytes exceeds " "the limit of %lld bytes", content_length, VirtualHost::GetMaxPostSize()); needDelete = read_all_post_data(transport, data, size); } else { if (transport->hasMorePostData()) { needDelete = true; data = Util::buffer_duplicate(data, size); } DecodeRfc1867(transport, g->GV(_POST), g->GV(_FILES), content_length, data, size, boundary); } ASSERT(!transport->getFiles(files)); } 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; // Always decode data for now. (macvicar) decodeData = true; if (decodeData) { DecodeParameters(g->GV(_POST), (const char*)data, size, true); } bool ret = transport->getFiles(files); if (ret) { g->GV(_FILES) = f_unserialize(files); } } CopyParams(request, g->GV(_POST)); if (needDelete) { if (RuntimeOption::AlwaysPopulateRawPostData) { g->GV(HTTP_RAW_POST_DATA) = String((char*)data, size, AttachString); } else { free((void *)data); } } else { // For literal we disregard RuntimeOption::AlwaysPopulateRawPostData g->GV(HTTP_RAW_POST_DATA) = String((char*)data, size, AttachLiteral); } } } // $_COOKIE string cookie_data = transport->getHeader("Cookie"); if (!cookie_data.empty()) { StringBuffer sb; sb.append(cookie_data); DecodeCookies(g->GV(_COOKIE), (char*)sb.data()); CopyParams(request, g->GV(_COOKIE)); } // $_SERVER // 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. HeaderMap headers; transport->getHeaders(headers); for (HeaderMap::const_iterator iter = headers.begin(); iter != headers.end(); ++iter) { const vector<string> &values = iter->second; for (unsigned int i = 0; i < values.size(); i++) { String key = "HTTP_"; key += StringUtil::ToUpper(iter->first).replace("-", "_"); server.set(key, String(values[i])); } } string host = transport->getHeader("Host"); String hostName(VirtualHost::GetCurrent()->serverName(host)); string hostHeader(host); if (hostHeader.empty()) { server.set("HTTP_HOST", hostName); StackTraceNoHeap::AddExtraLogging("Server", hostName.data()); } else { StackTraceNoHeap::AddExtraLogging("Server", hostHeader.c_str()); } 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); } } // APE sets CONTENT_TYPE and CONTENT_LENGTH without HTTP_ if (!contentType.empty()) { server.set("CONTENT_TYPE", String(contentType)); } if (!contentLength.empty()) { server.set("CONTENT_LENGTH", String(contentLength)); } // APE processes Authorization: Basic into PHP_AUTH_USER and PHP_AUTH_PW 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("PHP_AUTH_USER", decodedAuth.substr(0, colonPos)); server.set("PHP_AUTH_PW", decodedAuth.substr(colonPos + 1)); } } } server.set("REQUEST_URI", String(transport->getUrl(), CopyString)); server.set("SCRIPT_URL", r.originalURL()); String prefix(transport->isSSL() ? "https://" : "http://"); String port_suffix(""); // Need to append port if (!transport->isSSL() && RuntimeOption::ServerPort != 80) { port_suffix = ":" + RuntimeOption::ServerPort; } server.set("SCRIPT_URI", String(prefix + (hostHeader.empty() ? hostName + port_suffix : 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("SCRIPT_NAME", name); } else { server.set("SCRIPT_NAME", r.resolvedURL()); } if (!r.rewritten() && r.pathInfo().empty()) { server.set("PHP_SELF", r.resolvedURL()); } else { // when URL is rewritten, or pathinfo is not empty, use original URL server.set("PHP_SELF", r.originalURL()); } server.set("SCRIPT_FILENAME", r.absolutePath()); if (r.pathInfo().empty()) { server.set("PATH_TRANSLATED", r.absolutePath()); } else { server.set("PATH_TRANSLATED", String(vhost->getDocumentRoot() + r.pathInfo().data())); server.set("PATH_INFO", r.pathInfo()); } server.set("argv", r.queryString()); server.set("argc", 0); server.set("GATEWAY_INTERFACE", "CGI/1.1"); server.set("SERVER_ADDR", String(RuntimeOption::ServerPrimaryIP)); server.set("SERVER_NAME", hostName); server.set("SERVER_PORT", RuntimeOption::ServerPort); server.set("SERVER_SOFTWARE", "HPHP"); server.set("SERVER_PROTOCOL", "HTTP/" + transport->getHTTPVersion()); server.set("SERVER_ADMIN", ""); server.set("SERVER_SIGNATURE", ""); switch (transport->getMethod()) { case Transport::GET: server.set("REQUEST_METHOD", "GET"); break; case Transport::HEAD: server.set("REQUEST_METHOD", "HEAD"); break; case Transport::POST: if (transport->getExtendedMethod() == NULL) { server.set("REQUEST_METHOD", "POST"); } else { server.set("REQUEST_METHOD", transport->getExtendedMethod()); } break; default: server.set("REQUEST_METHOD", ""); break; } server.set("HTTPS", transport->isSSL() ? "1" : ""); server.set("REQUEST_TIME", time(NULL)); server.set("QUERY_STRING", r.queryString()); server.set("REMOTE_ADDR", String(transport->getRemoteHost(), CopyString)); server.set("REMOTE_HOST", ""); // I don't think we need to nslookup server.set("REMOTE_PORT", 0); // TODO: quite useless server.set("DOCUMENT_ROOT", String(vhost->getDocumentRoot())); for (map<string, string>::const_iterator iter = RuntimeOption::ServerVariables.begin(); iter != RuntimeOption::ServerVariables.end(); ++iter) { server.set(String(iter->first), String(iter->second)); } const map<string, string> &vServerVars = vhost->getServerVars(); for (map<string, string>::const_iterator iter = vServerVars.begin(); iter != vServerVars.end(); ++iter) { server.set(String(iter->first), String(iter->second)); } sri.setServerVariables(server); const char *threadType = transport->getThreadTypeName(); server.set("THREAD_TYPE", threadType); StackTraceNoHeap::AddExtraLogging("ThreadType", threadType); #ifdef TAINTED taint_array_variant(g->GV(_GET), "$_GET"); taint_array_variant(g->GV(_POST), "$_POST"); taint_array_variant(g->GV(_SERVER), "$_SERVER"); taint_array_variant(g->GV(_COOKIE), "$_COOKIE"); #endif }
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 assert(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(); } string hostHeader; if (server.exists(s_HTTP_HOST)) { hostHeader = server[s_HTTP_HOST].toCStrRef().data(); } String hostName; if (server.exists(s_SERVER_NAME)) { assert(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(); } 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 { assert(server.exists(s_DOCUMENT_ROOT)); assert(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() + 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); break; } server.set(s_HTTPS, transport->isSSL() ? s_on : empty_string); server.set(s_QUERY_STRING, r.queryString()); server.set(s_argv, make_packed_array(r.queryString())); server.set(s_argc, 1); }
void HttpProtocol::PrepareGetVariable(Array& get, const RequestURI &r) { DecodeParameters(get, r.queryString().data(), r.queryString().size()); }
/** * 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 g = get_global_variables()->asArrayData(); Variant emptyArr(staticEmptyArray()); for (auto& key : s_arraysToClear) { g->remove(key.get(), false); g->set(key.get(), emptyArr, false); } for (auto& key : s_arraysToUnset) { g->remove(key.get(), false); } // 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) { g->set(s_HTTP_RAW_POST_DATA, empty_string_variant_ref, false); } #define X(name) \ Array name##arr(Array::Create()); \ SCOPE_EXIT { g->set(s__##name, name##arr, false); }; 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) { g->set(s_HTTP_RAW_POST_DATA.get(), HTTP_RAW_POST_DATA, false); } }; auto variablesOrder = ThreadInfo::s_threadInfo.getNoCheck() ->m_reqInjectionData.getVariablesOrder(); auto requestOrder = ThreadInfo::s_threadInfo.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); 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 Array dummyPost(Array::Create()); Array dummyFiles(Array::Create()); PreparePostVariables(dummyPost, HTTP_RAW_POST_DATA, dummyFiles, transport); } PrepareRequestVariables(REQUESTarr, GETarr, POSTarr, COOKIEarr, requestOrder); }