b7cc57a8db
This gets rid of the (litstr) StringData and StackStringData constructors, but keeps String(litstr). Also rename all the instances of AttachLiteral to CopyString, since they now mean the same thing.
646 linhas
22 KiB
C++
646 linhas
22 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- 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 <runtime/base/hphp_system.h>
|
|
#include <runtime/base/server/http_protocol.h>
|
|
#include <runtime/base/zend/zend_url.h>
|
|
#include <runtime/base/zend/zend_string.h>
|
|
#include <runtime/base/program_functions.h>
|
|
#include <runtime/base/runtime_option.h>
|
|
#include <runtime/base/server/source_root_info.h>
|
|
#include <runtime/base/server/request_uri.h>
|
|
#include <runtime/base/server/transport.h>
|
|
#include <util/logger.h>
|
|
#include <util/util.h>
|
|
#include <runtime/base/server/upload.h>
|
|
#include <runtime/base/server/replay_transport.h>
|
|
#include <runtime/base/server/virtual_host.h>
|
|
#include <runtime/base/util/http_client.h>
|
|
|
|
#define DEFAULT_POST_CONTENT_TYPE "application/x-www-form-urlencoded"
|
|
|
|
using std::map;
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// helper functions
|
|
|
|
static bool read_all_post_data(Transport *transport,
|
|
const void *&data, int &size) {
|
|
if (transport->hasMorePostData()) {
|
|
data = Util::buffer_duplicate(data, size);
|
|
do {
|
|
int delta = 0;
|
|
const void *extra = transport->getMorePostData(delta);
|
|
if (size + delta < VirtualHost::GetMaxPostSize()) {
|
|
data = Util::buffer_append(data, size, extra, delta);
|
|
size += delta;
|
|
}
|
|
} while (transport->hasMorePostData());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
const VirtualHost *HttpProtocol::GetVirtualHost(Transport *transport) {
|
|
if (!RuntimeOption::VirtualHosts.empty()) {
|
|
string host = transport->getHeader("Host");
|
|
for (unsigned int i = 0; i < RuntimeOption::VirtualHosts.size(); i++) {
|
|
VirtualHostPtr vhost = RuntimeOption::VirtualHosts[i];
|
|
if (vhost->match(host)) {
|
|
VirtualHost::SetCurrent(vhost.get());
|
|
return vhost.get();
|
|
}
|
|
}
|
|
}
|
|
VirtualHost::SetCurrent(nullptr);
|
|
return VirtualHost::GetCurrent();
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
|
|
Variant &server = g->GV(_SERVER);
|
|
server.set("REQUEST_START_TIME", time(nullptr));
|
|
|
|
// $_ENV
|
|
process_env_variables(g->GV(_ENV));
|
|
g->GV(_ENV).set("HPHP", 1);
|
|
g->GV(_ENV).set("HHVM", 1);
|
|
if (RuntimeOption::EvalJit) {
|
|
g->GV(_ENV).set("HHVM_JIT", 1);
|
|
}
|
|
|
|
bool isServer = RuntimeOption::serverExecutionMode();
|
|
if (isServer) {
|
|
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());
|
|
while (transport->hasMorePostData()) {
|
|
int delta = 0;
|
|
transport->getMorePostData(delta);
|
|
}
|
|
} 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) = unserialize_from_string(files);
|
|
}
|
|
}
|
|
CopyParams(request, g->GV(_POST));
|
|
if (needDelete) {
|
|
if (RuntimeOption::AlwaysPopulateRawPostData &&
|
|
uint32_t(size) <= StringData::MaxSize) {
|
|
g->GV(HTTP_RAW_POST_DATA) = String((char*)data, size, AttachString);
|
|
} else {
|
|
free((void *)data);
|
|
}
|
|
} else {
|
|
// For literal we disregard RuntimeOption::AlwaysPopulateRawPostData
|
|
if (uint32_t(size) <= StringData::MaxSize) {
|
|
g->GV(HTTP_RAW_POST_DATA) = String((char*)data, size, CopyString);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// $_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);
|
|
|
|
static int bad_request_count = -1;
|
|
for (HeaderMap::const_iterator iter = headers.begin();
|
|
iter != headers.end(); ++iter) {
|
|
const vector<string> &values = iter->second;
|
|
|
|
// 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) {
|
|
String key = "HTTP_";
|
|
key += StringUtil::ToUpper(iter->first).replace("-","_");
|
|
if (server.asArrRef().exists(key)) {
|
|
if (!(++bad_request_count % RuntimeOption::LogHeaderMangle)) {
|
|
Logger::Warning(
|
|
"HeaderMangle warning: "
|
|
"The header %s overwrote another header 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.",
|
|
iter->first.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
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 :
|
|
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("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() == nullptr) {
|
|
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(nullptr));
|
|
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", transport->getRemotePort());
|
|
|
|
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);
|
|
}
|
|
|
|
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::CopyParams(Variant &dest, Variant &src) {
|
|
if (src.isArray()) {
|
|
Array srcArray = src.toArray();
|
|
for (ArrayIter iter(srcArray); iter; ++iter) {
|
|
dest.set(iter.first(), iter.second());
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpProtocol::DecodeParameters(Variant &variables, const char *data,
|
|
int 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)))) {
|
|
int len = val - s;
|
|
char *name = url_decode(s, len);
|
|
String sname(name, len, AttachString);
|
|
|
|
val++;
|
|
len = p - val;
|
|
char *value = url_decode(val, len);
|
|
if (RuntimeOption::EnableMagicQuotesGpc) {
|
|
char *slashedvalue = string_addslashes(value, len);
|
|
free(value);
|
|
value = slashedvalue;
|
|
}
|
|
String svalue(value, len, AttachString);
|
|
|
|
register_variable(variables, (char*)sname.data(), svalue);
|
|
} else if (!post) {
|
|
int len = p - s;
|
|
char *name = url_decode(s, len);
|
|
String sname(name, len, AttachString);
|
|
register_variable(variables, (char*)sname.data(), "");
|
|
}
|
|
s = p + 1;
|
|
}
|
|
if (s < e) {
|
|
p = e;
|
|
goto last_value;
|
|
}
|
|
}
|
|
|
|
void HttpProtocol::DecodeCookies(Variant &variables, char *data) {
|
|
assert(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 */
|
|
int len = val - var;
|
|
char *name = url_decode(var, len);
|
|
String sname(name, len, AttachString);
|
|
|
|
++val;
|
|
len = strlen(val);
|
|
char *value = url_decode(val, len);
|
|
if (RuntimeOption::EnableMagicQuotesGpc) {
|
|
char *slashedvalue = string_addslashes(value, len);
|
|
free(value);
|
|
value = slashedvalue;
|
|
}
|
|
String svalue(value, len, AttachString);
|
|
|
|
register_variable(variables, (char*)sname.data(), svalue, false);
|
|
} else {
|
|
int len = strlen(var);
|
|
char *name = url_decode(var, len);
|
|
String sname(name, len, AttachString);
|
|
|
|
register_variable(variables, (char*)sname.data(), "", false);
|
|
}
|
|
}
|
|
|
|
var = strtok_r(nullptr, ";", &strtok_buf);
|
|
}
|
|
}
|
|
|
|
bool HttpProtocol::IsRfc1867(const string contentType, 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 = strchr(s, ',');
|
|
}
|
|
if (e) {
|
|
e[0] = '\0';
|
|
}
|
|
boundary = s;
|
|
return true;
|
|
}
|
|
|
|
void HttpProtocol::DecodeRfc1867(Transport *transport,
|
|
Variant &post, Variant &files,
|
|
int contentLength, const void *&data,
|
|
int &size, string boundary) {
|
|
rfc1867PostHandler(transport, post, files, contentLength,
|
|
data, size, boundary);
|
|
}
|
|
|
|
const char *HttpProtocol::GetReasonString(int code) {
|
|
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 207: return "Multi-Status";
|
|
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 Time-out";
|
|
case 409: return "Conflict";
|
|
case 410: return "Gone";
|
|
case 411: return "Length Required";
|
|
case 412: return "Precondition Failed";
|
|
case 413: return "Request Entity Too Large";
|
|
case 414: return "Request-URI Too Large";
|
|
case 415: return "Unsupported Media Type";
|
|
case 416: return "Requested 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 Time-out";
|
|
case 505: return "HTTP Version not supported";
|
|
}
|
|
return "Bad Response";
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool HttpProtocol::ProxyRequest(Transport *transport, bool force,
|
|
const std::string &url,
|
|
int &code, std::string &error,
|
|
StringBuffer &response,
|
|
HeaderMap *extraHeaders /* = NULL */) {
|
|
assert(transport);
|
|
if (transport->headersSent()) {
|
|
raise_warning("Cannot proxy request - headers already sent");
|
|
return false;
|
|
}
|
|
|
|
HeaderMap requestHeaders;
|
|
transport->getHeaders(requestHeaders);
|
|
if (extraHeaders) {
|
|
for (HeaderMap::const_iterator iter = extraHeaders->begin();
|
|
iter != extraHeaders->end(); ++iter) {
|
|
vector<string> &values = requestHeaders[iter->first];
|
|
values.insert(values.end(), iter->second.begin(), iter->second.end());
|
|
}
|
|
}
|
|
|
|
int size = 0;
|
|
const char *data = nullptr;
|
|
if (transport->getMethod() == Transport::POST) {
|
|
data = (const char *)transport->getPostData(size);
|
|
}
|
|
|
|
code = 0; // HTTP status of curl or 0 for "no server response code"
|
|
vector<String> responseHeaders;
|
|
HttpClient http;
|
|
if (data && size) {
|
|
code = http.post(url.c_str(), data, size, response, &requestHeaders,
|
|
&responseHeaders);
|
|
} else {
|
|
code = http.get(url.c_str(), 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;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|