ac91741c0f
When the same header is passed in, Apache/PHP will comma delimit all header values for the returned values in apache_request_headers and $_SERVER['HTTP_*'].
714 linhas
24 KiB
C++
714 linhas
24 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 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/base/server/http_protocol.h"
|
|
#include "hphp/runtime/base/hphp_system.h"
|
|
#include "hphp/runtime/base/zend/zend_url.h"
|
|
#include "hphp/runtime/base/zend/zend_string.h"
|
|
#include "hphp/runtime/base/program_functions.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/server/source_root_info.h"
|
|
#include "hphp/runtime/base/server/request_uri.h"
|
|
#include "hphp/runtime/base/server/transport.h"
|
|
#include "hphp/util/logger.h"
|
|
#include "hphp/util/util.h"
|
|
#include "hphp/runtime/base/server/upload.h"
|
|
#include "hphp/runtime/base/server/replay_transport.h"
|
|
#include "hphp/runtime/base/server/virtual_host.h"
|
|
#include "hphp/runtime/base/util/http_client.h"
|
|
#include "folly/String.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();
|
|
}
|
|
|
|
const StaticString
|
|
s_REQUEST_START_TIME("REQUEST_START_TIME"),
|
|
s_HPHP("HPHP"),
|
|
s_HHVM("HHVM"),
|
|
s_HHVM_JIT("HHVM_JIT"),
|
|
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_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_1("1"),
|
|
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");
|
|
|
|
/**
|
|
* 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) {
|
|
GlobalVariables *g = get_global_variables();
|
|
const VirtualHost *vhost = VirtualHost::GetCurrent();
|
|
|
|
Variant& server = g->getRef(s__SERVER);
|
|
server.set(s_REQUEST_START_TIME, time(nullptr));
|
|
|
|
// $_ENV
|
|
Variant& env = g->getRef(s__ENV);
|
|
process_env_variables(env);
|
|
env.set(s_HPHP, 1);
|
|
env.set(s_HHVM, 1);
|
|
if (RuntimeOption::EvalJit) {
|
|
env.set(s_HHVM_JIT, 1);
|
|
}
|
|
|
|
bool isServer = RuntimeOption::ServerExecutionMode();
|
|
if (isServer) {
|
|
env.set(s_HPHP_SERVER, 1);
|
|
#ifdef HOTPROFILER
|
|
env.set(s_HPHP_HOTPROFILER, 1);
|
|
#endif
|
|
}
|
|
|
|
Variant &request = g->getRef(s__REQUEST);
|
|
|
|
// $_GET and $_REQUEST
|
|
if (!r.queryString().empty()) {
|
|
Variant &get = g->getRef(s__GET);
|
|
DecodeParameters(get, r.queryString().data(),
|
|
r.queryString().size());
|
|
CopyParams(request, get);
|
|
}
|
|
|
|
string contentType = transport->getHeader("Content-Type");
|
|
string contentLength = transport->getHeader("Content-Length");
|
|
// $_POST and $_REQUEST
|
|
if (transport->getMethod() == Transport::Method::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 %" PRId64 " 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->getRef(s__POST), g->getRef(s__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->getRef(s__POST), (const char*)data, size, true);
|
|
}
|
|
|
|
bool ret = transport->getFiles(files);
|
|
if (ret) {
|
|
g->getRef(s__FILES) = unserialize_from_string(files);
|
|
}
|
|
}
|
|
CopyParams(request, g->getRef(s__POST));
|
|
if (needDelete) {
|
|
if (RuntimeOption::AlwaysPopulateRawPostData &&
|
|
uint32_t(size) <= StringData::MaxSize) {
|
|
g->getRef(s_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->getRef(s_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->getRef(s__COOKIE), (char*)sb.data());
|
|
CopyParams(request, g->getRef(s__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());
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
String key = "HTTP_";
|
|
key += StringUtil::ToUpper(iter->first).replace("-", "_");
|
|
server.set(key, String(folly::join(", ", values)));
|
|
}
|
|
}
|
|
|
|
string host = transport->getHeader("Host");
|
|
String hostName(VirtualHost::GetCurrent()->serverName(host));
|
|
string hostHeader(host);
|
|
if (hostHeader.empty()) {
|
|
server.set(s_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(s_CONTENT_TYPE, String(contentType));
|
|
}
|
|
if (!contentLength.empty()) {
|
|
server.set(s_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(s_PHP_AUTH_USER, decodedAuth.substr(0, colonPos));
|
|
server.set(s_PHP_AUTH_PW, decodedAuth.substr(colonPos + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
server.set(s_REQUEST_URI, String(transport->getUrl(), CopyString));
|
|
server.set(s_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 = folly::format(":{}", RuntimeOption::ServerPort).str();
|
|
}
|
|
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());
|
|
}
|
|
|
|
server.set(s_SCRIPT_FILENAME, r.absolutePath());
|
|
if (r.pathInfo().empty()) {
|
|
server.set(s_PATH_TRANSLATED, r.absolutePath());
|
|
} else {
|
|
server.set(s_PATH_TRANSLATED,
|
|
String(vhost->getDocumentRoot() + r.pathInfo().data()));
|
|
server.set(s_PATH_INFO, r.pathInfo());
|
|
}
|
|
|
|
server.set(s_argv, CREATE_VECTOR1(r.queryString()));
|
|
server.set(s_argc, 1);
|
|
server.set(s_GATEWAY_INTERFACE, s_CGI_1_1);
|
|
server.set(s_SERVER_ADDR, String(RuntimeOption::ServerPrimaryIP));
|
|
server.set(s_SERVER_NAME, hostName);
|
|
server.set(s_SERVER_PORT, RuntimeOption::ServerPort);
|
|
server.set(s_SERVER_SOFTWARE, s_HPHP);
|
|
server.set(s_SERVER_PROTOCOL, "HTTP/" + transport->getHTTPVersion());
|
|
server.set(s_SERVER_ADMIN, empty_string);
|
|
server.set(s_SERVER_SIGNATURE, empty_string);
|
|
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;
|
|
}
|
|
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_HTTPS, transport->isSSL() ? s_1 : empty_string);
|
|
server.set(s_REQUEST_TIME, now);
|
|
server.set(s_REQUEST_TIME_FLOAT, now_double);
|
|
server.set(s_QUERY_STRING, r.queryString());
|
|
|
|
server.set(s_REMOTE_ADDR, String(transport->getRemoteHost(), CopyString));
|
|
server.set(s_REMOTE_HOST, empty_string); // I don't think we need to nslookup
|
|
server.set(s_REMOTE_PORT, transport->getRemotePort());
|
|
|
|
server.set(s_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(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::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::Method::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;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|