am_status_t BaseService::doRequest(const ServiceInfo& service, const BodyChunk& headerPrefix, const std::string& uriParameters, const Http::CookieList& cookieList, const BodyChunk& headerSuffix, const BodyChunkList& bodyChunkList, Http::Response& response, std::size_t initialBufferLen, const std::string &cert_nick_name, const ServerInfo** serverInfo) const { am_status_t status = AM_SERVICE_NOT_AVAILABLE; std::size_t dataLen = 0; // Create a temporary buffer for the Content-Line header // the extra '2' is for the <CR><LF> at the end. The // sizeof the CONTENT_LENGTH_HDR includes space for the // terminating NUL. char contentLine[sizeof(CONTENT_LENGTH_HDR) + (sizeof(dataLen) * DIGITS_PER_BYTE) + 2]; std::size_t contentLineLen; for (unsigned int i = 0; i < bodyChunkList.size(); ++i) { dataLen += bodyChunkList[i].data.size(); } contentLineLen = snprintf(contentLine, sizeof(contentLine), "%s%d\r\n", CONTENT_LENGTH_HDR, dataLen); if (sizeof(contentLine) > contentLineLen) { BodyChunk contentLineChunk(contentLine, contentLineLen); ServiceInfo::const_iterator iter; for (iter = service.begin(); iter != service.end(); ++iter) { ServerInfo svrInfo = ServerInfo((const ServerInfo&)(*iter)); if (!svrInfo.isHealthy(poll_primary_server)) { Log::log(logModule, Log::LOG_WARNING, "BaseService::doRequest(): " "Server is unavailable: %s.", svrInfo.getURL().c_str()); continue; } else { Log::log(logModule, Log::LOG_DEBUG, "BaseService::doRequest(): Using server: %s.", iter->getURL().c_str()); } Http::HeaderList headerList, proxyHeaderList; Http::Cookie hostHeader("Host", svrInfo.getHost()); headerList.push_back(hostHeader); if (useProxy) { proxyHeaderList.push_back(hostHeader); // Override (temporarily) server credentials if using proxy svrInfo.setHost(proxyHost); svrInfo.setPort(proxyPort); // We don't use SSL for initial proxy connection svrInfo.setUseSSL(false); Log::log(logModule, Log::LOG_DEBUG, "BaseService::doRequest(): Using proxy: %s:%d", proxyHost.c_str(),proxyPort); // Add Proxy-Authorization header if user defined if (useProxyAuth) { // allocate enough for a base64-encoded digest int authSize = proxyUser.size() + proxyPassword.size() + 1; // 11 extra bytes for prefix and terminator char * digest = (char *)malloc(authSize * 4/3 + 11); strcpy(digest, "Basic "); encode_base64((proxyUser + ":" + proxyPassword).c_str(), authSize,(digest + 6)); Log::log(logModule, Log::LOG_MAX_DEBUG, "BaseService::doRequest(): Using proxy auth as: %s", proxyUser.c_str()); hostHeader = Http::Cookie("Proxy-Authorization", digest); proxyHeaderList.push_back(hostHeader); free(digest); } } // retry to connect to server before marking it as down. // making the number of attempts configurable may have a negative // side effect on performance, if the the value is a high number. int retryAttempts = 3; int retryCount = 0; while(retryCount < retryAttempts) { retryCount++; try { Connection conn(svrInfo, certDBPasswd, (cert_nick_name.size()>0)?cert_nick_name:certNickName, alwaysTrustServerCert); const char *operation = "sending to"; // in case proxy is defined and target URL is HTTPS, // establish an SSL tunnel first send // CONNECT host:port string if (useProxy && iter->useSSL()) { SECStatus secStatus = SECFailure; // All the other parameters would be empty for a // proxy CONNECT Http::CookieList emptyCookieList; BodyChunk emptyChunk; BodyChunkList emptyChunkList; // Add a Keep-alive header since we're using HTTP/1.0 hostHeader = Http::Cookie("Connection", "Keep-Alive\r\n"); proxyHeaderList.push_back(hostHeader); status = sendRequest(conn, BodyChunk(std::string("CONNECT ")), iter->getHost() + ":" + Utils::toString(iter->getPort()), std::string(""), proxyHeaderList, emptyCookieList, emptyChunk, emptyChunk, emptyChunkList); if (status == AM_SUCCESS) { // Retrieve proxie's response if tunnel // established (void) response.readAndIgnore(logModule, conn); // Secure the tunnel now by upgrading the socket PRFileDesc *sock = conn.secureSocket( certDBPasswd, (cert_nick_name.size()>0)? cert_nick_name:certNickName, alwaysTrustServerCert, NULL); if (sock != static_cast<PRFileDesc *>(NULL)) { secStatus = SSL_SetURL(sock, iter->getHost().c_str()); } } if (status != AM_SUCCESS || SECSuccess != secStatus){ Log::log(logModule, Log::LOG_ERROR, "BaseService::doRequest(): could not " "establish a secure proxy tunnel"); // Can't continue and mark server as down as // it was a proxy failure return AM_FAILURE; } } if(Log::isLevelEnabled(logModule, Log::LOG_MAX_DEBUG)) { std::string commString; for(std::size_t i = 0; i<bodyChunkList.size(); ++i) { if(!bodyChunkList[i].secure) { commString.append(bodyChunkList[i].data); } else { commString.append("<secure data>"); } } for(std::size_t commPos = commString.find("%"); commPos != std::string::npos && commPos < commString.size(); commPos = commString.find("%", commPos)) { commString.replace(commPos, 1, "%%"); commPos += 2; } Log::log(logModule, Log::LOG_MAX_DEBUG, commString.c_str()); } std::string requestString = iter->getURI(); /* * In case the following request would go to a proxy * we need to use full URL and special headers. * If the resource is HTTPS, we're not posting our * request to the proxy, but to the server * through proxy tunnel */ if (useProxy && !(iter->useSSL())) { requestString = iter->getURL(); headerList = proxyHeaderList; } status = sendRequest(conn, headerPrefix, requestString, uriParameters, headerList, cookieList, contentLineChunk, headerSuffix, bodyChunkList); if (AM_SUCCESS == status) { operation = "receiving from"; status = response.readAndParse(logModule, conn, initialBufferLen); if (AM_SUCCESS == status) { Log::log(logModule, Log::LOG_MAX_DEBUG, "%.*s", response.getBodyLen(), response.getBodyPtr()); } } if (AM_NSPR_ERROR == status) { PRErrorCode nspr_code = PR_GetError(); Log::log(logModule, Log::LOG_ALWAYS, "BaseService::doRequest() NSPR failure while " "%s %s, error = %s", operation, (*iter).toString().c_str(), PR_ErrorToName(nspr_code)); } if (AM_SUCCESS == status) { if(serverInfo != NULL) *serverInfo = &(*iter); break; } else { if(retryCount < retryAttempts) { continue; } else { Log::log(logModule, Log::LOG_DEBUG, "BaseService::doRequest() Invoking markSeverDown"); svrInfo.markServerDown(poll_primary_server); } } } catch (const NSPRException& exc) { Log::log(logModule, Log::LOG_DEBUG, "BaseService::doRequest() caught %s: %s called by %s " "returned %s", exc.what(), exc.getNsprMethod(), exc.getThrowingMethod(), PR_ErrorToName(exc.getErrorCode())); if(retryCount < retryAttempts) { status = AM_NSPR_ERROR; continue; } else { Log::log(logModule, Log::LOG_DEBUG, "BaseService::doRequest() Invoking markSeverDown"); svrInfo.markServerDown(poll_primary_server); status = AM_NSPR_ERROR; } } } //end of while if (AM_SUCCESS == status) { if(serverInfo != NULL) *serverInfo = &(*iter); break; } if (status = AM_NSPR_ERROR) { continue; } } // end of for } else { status = AM_BUFFER_TOO_SMALL; } return status; }
/** * 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 }
am_status_t BaseService::doRequest(const ServiceInfo& service, const BodyChunk& headerPrefix, const std::string& uriParameters, const Http::CookieList& cookieList, const BodyChunk& headerSuffix, const BodyChunkList& bodyChunkList, Http::Response& response, const ServerInfo** serverInfo) const { am_status_t status = AM_SERVICE_NOT_AVAILABLE; int dataLen = 0; BodyChunk emptyContentLine; // Create a temporary buffer for the Content-Line header // the extra '2' is for the <CR><LF> at the end. The // sizeof the CONTENT_LENGTH_HDR includes space for the // terminating NUL. const std::size_t contentLineLen_sz = sizeof (CONTENT_LENGTH_HDR) + (sizeof (dataLen) * DIGITS_PER_BYTE) + 2; char contentLine[contentLineLen_sz + 1]; std::size_t contentLineLen = 0; for (unsigned int i = 0; i < bodyChunkList.size(); ++i) { dataLen += bodyChunkList[i].data.size(); } memset(&contentLine[0], 0, contentLineLen_sz); contentLineLen = snprintf(contentLine, contentLineLen_sz, "%s%d\r\n", CONTENT_LENGTH_HDR, dataLen); if (contentLineLen_sz > contentLineLen && contentLineLen > 0) { BodyChunk contentLineChunk(contentLine, contentLineLen); ServiceInfo::const_iterator iter; ServiceInfo svc(service); int j = -1, current_index = -1; if (am_web_naming_validation_status() < 2 && svc.getNumberOfServers() > 1) { /* validation is enabled and number of servers is more than one */ char *current_value = read_naming_value(AM_NAMING_LOCK, Log::getLockId()); if (current_value != NULL) { current_index = strtol(current_value, NULL, 10); if (current_index < 0 || errno == ERANGE) { current_index = 0; } else { Log::log(logModule, Log::LOG_MAX_DEBUG, "BaseService::doRequest(): will be using url index: %d", current_index); } free(current_value); } else { current_index = 0; } if ((size_t) current_index >= svc.getNumberOfServers()) { Log::log(logModule, Log::LOG_WARNING, "BaseService::doRequest(): invalid url index: %d (out of %d); validation results ignored.", current_index, svc.getNumberOfServers()); current_index = -1; } } for (iter = svc.begin(); iter != svc.end(); ++iter) { const ServerInfo &svrInfo = (*iter); if (current_index != -1) { if ((++j) != current_index) { Log::log(logModule, Log::LOG_MAX_DEBUG, "BaseService::doRequest(): skipping url(%d) %s", j, svrInfo.getURL().c_str()); continue; } else { Log::log(logModule, Log::LOG_MAX_DEBUG, "BaseService::doRequest(): using url(%d) %s", j, svrInfo.getURL().c_str()); } } Http::HeaderList headerList, proxyHeaderList; Http::Cookie hostHeader("Host", svrInfo.getHost()); headerList.push_back(hostHeader); #ifdef AGENT_PROXY_SUPPORT #ifndef _MSC_VER if (useProxy) { proxyHeaderList.push_back(hostHeader); // Override (temporarily) server credentials if using proxy //svrInfo.setHost(proxyHost); //svrInfo.setPort(proxyPort); // We don't use SSL for initial proxy connection //svrInfo.setUseSSL(false); Log::log(logModule, Log::LOG_DEBUG, "BaseService::doRequest(): Using proxy: %s:%d", proxyHost.c_str(), proxyPort); // Add Proxy-Authorization header if user defined if (useProxyAuth) { // allocate enough for a base64-encoded digest size_t authSize = proxyUser.size() + proxyPassword.size() + 1; // 11 extra bytes for prefix and terminator char * digest = (char *) malloc(authSize * 4 / 3 + 11); strcpy(digest, "Basic "); encode_base64((proxyUser + ":" + proxyPassword).c_str(), authSize, (digest + 6)); Log::log(logModule, Log::LOG_MAX_DEBUG, "BaseService::doRequest(): Using proxy auth as: %s", proxyUser.c_str()); hostHeader = Http::Cookie("Proxy-Authorization", digest); proxyHeaderList.push_back(hostHeader); free(digest); } } #endif #endif // retry to connect to server before marking it as down. // making the number of attempts configurable may have a negative // side effect on performance, if the the value is a high number. int retryAttempts = 3; int retryCount = 0; while (retryCount < retryAttempts) { retryCount++; try { const char *operation = "sending to"; Connection conn(svrInfo.getHost().c_str(), svrInfo.getPort(), svrInfo.useSSL()); #ifdef AGENT_PROXY_SUPPORT std::string requestString = svrInfo.getURI().c_str(); /* * In case the following request would go to a proxy * we need to use full URL and special headers. */ if (useProxy && !(iter->useSSL())) { requestString = iter->getURL(); headerList = proxyHeaderList; } #endif status = sendRequest(conn, headerPrefix, svrInfo.getURI(), uriParameters, headerList, cookieList, dataLen > 0 ? contentLineChunk : emptyContentLine, headerSuffix, bodyChunkList); if (AM_SUCCESS == status) { operation = "receiving from"; status = response.readAndParse(logModule, conn); if (AM_SUCCESS == status) { Log::log(logModule, Log::LOG_MAX_DEBUG, "Response::readAndParse() (%d) %s", response.getBodyLen(), response.getBodyPtr() ? response.getBodyPtr() : "(NULL)"); } } if (AM_NSPR_ERROR == status) { Log::log(logModule, Log::LOG_ALWAYS, "BaseService::doRequest() NSPR failure while " "%s %s", operation, svrInfo.getURL().c_str()); } if (AM_SUCCESS == status) { break; } else { if (retryCount < retryAttempts) { continue; } else { Log::log(logModule, Log::LOG_DEBUG, "BaseService::doRequest() Invoking markSeverDown"); /*svrInfo.markServerDown(poll_primary_server);*/ } } } catch (const NSPRException& exc) { Log::log(logModule, Log::LOG_ERROR, "BaseService::doRequest() caught %s: %s called by %s", exc.what(), exc.getNsprMethod(), exc.getThrowingMethod()); if (retryCount < retryAttempts) { status = AM_NSPR_ERROR; continue; } else { Log::log(logModule, Log::LOG_ERROR, "BaseService::doRequest() Invoking markSeverDown"); /*svrInfo.markServerDown(poll_primary_server);*/ status = AM_NSPR_ERROR; } } } //end of while if (AM_SUCCESS == status) { break; } } // end of for } else { status = AM_BUFFER_TOO_SMALL; } return status; }