450 linhas
14 KiB
C++
450 linhas
14 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/virtual_host.h"
|
|
|
|
#include "hphp/runtime/base/comparisons.h"
|
|
#include "hphp/runtime/base/preg.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/comparisons.h"
|
|
#include "hphp/runtime/base/timeout_thread.h"
|
|
#include "hphp/runtime/base/string_util.h"
|
|
#include "hphp/util/util.h"
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
VirtualHost &VirtualHost::GetDefault() {
|
|
// VirtualHost acquires global mutexes in its constructor, so we allocate
|
|
// s_default_vhost lazily to ensure that all of the global mutexes have
|
|
// been initialized before we enter the constructor.
|
|
static VirtualHost s_default_vhost;
|
|
return s_default_vhost;
|
|
}
|
|
|
|
void VirtualHost::SetCurrent(VirtualHost *vhost) {
|
|
g_context->setVirtualHost(vhost ? vhost : &VirtualHost::GetDefault());
|
|
}
|
|
|
|
const VirtualHost *VirtualHost::GetCurrent() {
|
|
const VirtualHost *ret = g_context->getVirtualHost();
|
|
if (!ret) ret = &VirtualHost::GetDefault();
|
|
return ret;
|
|
}
|
|
|
|
int64_t VirtualHost::GetMaxPostSize() {
|
|
const VirtualHost *vh = GetCurrent();
|
|
assert(vh);
|
|
if (vh->m_runtimeOption.maxPostSize != -1) {
|
|
return vh->m_runtimeOption.maxPostSize;
|
|
}
|
|
return RuntimeOption::MaxPostSize;
|
|
}
|
|
|
|
int64_t VirtualHost::GetUploadMaxFileSize() {
|
|
const VirtualHost *vh = GetCurrent();
|
|
assert(vh);
|
|
if (vh->m_runtimeOption.uploadMaxFileSize != -1) {
|
|
return vh->m_runtimeOption.uploadMaxFileSize;
|
|
}
|
|
return RuntimeOption::UploadMaxFileSize;
|
|
}
|
|
|
|
const vector<string> &VirtualHost::GetAllowedDirectories() {
|
|
const VirtualHost *vh = GetCurrent();
|
|
assert(vh);
|
|
if (!vh->m_runtimeOption.allowedDirectories.empty()) {
|
|
return vh->m_runtimeOption.allowedDirectories;
|
|
}
|
|
return RuntimeOption::AllowedDirectories;
|
|
}
|
|
|
|
void VirtualHost::SortAllowedDirectories(std::vector<std::string>& dirs) {
|
|
/*
|
|
Make sure corresponding realpath's are also allowed
|
|
*/
|
|
for (unsigned int i = 0, s = dirs.size(); i < s; i++) {
|
|
string &directory = dirs[i];
|
|
char resolved_path[PATH_MAX];
|
|
if (realpath(directory.c_str(), resolved_path) &&
|
|
directory != resolved_path) {
|
|
dirs.push_back(resolved_path);
|
|
}
|
|
}
|
|
/*
|
|
sort so we can use upper_bound to find the right prefix,
|
|
rather than using a linear scan (and so we can remove
|
|
duplicates, etc below)
|
|
*/
|
|
std::sort(dirs.begin(), dirs.end());
|
|
/*
|
|
AllowedDirectories is a list of prefixes, so if x is a substring
|
|
of y, we dont need y (also remove any duplicates).
|
|
*/
|
|
dirs.erase(std::unique(dirs.begin(), dirs.end(),
|
|
[](const std::string &a, const std::string &b) {
|
|
if (a.size() < b.size()) {
|
|
return !b.compare(0, a.size(), a);
|
|
}
|
|
return !a.compare(0, b.size(), b);
|
|
}),
|
|
dirs.end());
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void VirtualHost::initRuntimeOption(Hdf overwrite) {
|
|
int requestTimeoutSeconds =
|
|
overwrite["Server.RequestTimeoutSeconds"].getInt32(-1);
|
|
int64_t maxPostSize =
|
|
overwrite["Server.MaxPostSize"].getInt32(-1);
|
|
if (maxPostSize != -1) maxPostSize *= (1LL << 20);
|
|
int64_t uploadMaxFileSize =
|
|
overwrite["Server.Upload.UploadMaxFileSize"].getInt32(-1);
|
|
if (uploadMaxFileSize != -1) uploadMaxFileSize *= (1LL << 20);
|
|
overwrite["Server.AllowedDirectories"].
|
|
get(m_runtimeOption.allowedDirectories);
|
|
m_runtimeOption.requestTimeoutSeconds = requestTimeoutSeconds;
|
|
m_runtimeOption.maxPostSize = maxPostSize;
|
|
m_runtimeOption.uploadMaxFileSize = uploadMaxFileSize;
|
|
}
|
|
|
|
void VirtualHost::addAllowedDirectories(const std::vector<std::string>& dirs) {
|
|
if (!m_runtimeOption.allowedDirectories.empty()) {
|
|
m_runtimeOption.allowedDirectories.insert(
|
|
m_runtimeOption.allowedDirectories.end(),
|
|
dirs.begin(), dirs.end());
|
|
SortAllowedDirectories(m_runtimeOption.allowedDirectories);
|
|
}
|
|
}
|
|
|
|
void VirtualHost::setRequestTimeoutSeconds() const {
|
|
if (m_runtimeOption.requestTimeoutSeconds != -1) {
|
|
TimeoutThread::DeferTimeout(m_runtimeOption.requestTimeoutSeconds);
|
|
}
|
|
}
|
|
|
|
VirtualHost::VirtualHost() : m_disabled(false) {
|
|
Hdf empty;
|
|
initRuntimeOption(empty);
|
|
}
|
|
|
|
VirtualHost::VirtualHost(Hdf vh) : m_disabled(false) {
|
|
init(vh);
|
|
}
|
|
|
|
void VirtualHost::init(Hdf vh) {
|
|
m_name = vh.getName();
|
|
|
|
const char *prefix = vh["Prefix"].get("");
|
|
const char *pattern = vh["Pattern"].get("");
|
|
const char *pathTranslation = vh["PathTranslation"].get("");
|
|
Hdf overwrite = vh["overwrite"];
|
|
initRuntimeOption(overwrite);
|
|
|
|
if (prefix) m_prefix = prefix;
|
|
if (pattern) {
|
|
m_pattern = Util::format_pattern(pattern, true);
|
|
if (!m_pattern.empty()) {
|
|
m_pattern += "i"; // case-insensitive
|
|
}
|
|
}
|
|
if (pathTranslation) {
|
|
m_pathTranslation = pathTranslation;
|
|
if (!m_pathTranslation.empty() &&
|
|
m_pathTranslation[m_pathTranslation.length() - 1] != '/') {
|
|
m_pathTranslation += '/';
|
|
}
|
|
}
|
|
m_disabled = vh["Disabled"].getBool(false);
|
|
|
|
m_documentRoot = RuntimeOption::SourceRoot + m_pathTranslation;
|
|
if (!m_documentRoot.empty() &&
|
|
m_documentRoot[m_documentRoot.length() - 1] == '/') {
|
|
m_documentRoot = m_documentRoot.substr(0, m_documentRoot.length() - 1);
|
|
}
|
|
|
|
Hdf rewriteRules = vh["RewriteRules"];
|
|
for (Hdf hdf = rewriteRules.firstChild(); hdf.exists(); hdf = hdf.next()) {
|
|
RewriteRule dummy;
|
|
m_rewriteRules.push_back(dummy);
|
|
RewriteRule &rule = m_rewriteRules.back();
|
|
rule.pattern = Util::format_pattern(hdf["pattern"].getString(""), true);
|
|
rule.to = hdf["to"].getString("");
|
|
rule.qsa = hdf["qsa"].getBool(false);
|
|
rule.redirect = hdf["redirect"].getInt16(0);
|
|
rule.encode_backrefs = hdf["encode_backrefs"].getBool(false);
|
|
|
|
if (rule.pattern.empty() || rule.to.empty()) {
|
|
throw InvalidArgumentException("rewrite rule", "(empty pattern or to)");
|
|
}
|
|
Hdf rewriteConds = hdf["conditions"];
|
|
for (Hdf chdf = rewriteConds.firstChild(); chdf.exists();
|
|
chdf = chdf.next()) {
|
|
RewriteCond dummy;
|
|
rule.rewriteConds.push_back(dummy);
|
|
RewriteCond &cond = rule.rewriteConds.back();
|
|
cond.pattern = Util::format_pattern(chdf["pattern"].getString(""), true);
|
|
if (cond.pattern.empty()) {
|
|
throw InvalidArgumentException("rewrite rule", "(empty cond pattern)");
|
|
}
|
|
const char *type = chdf["type"].get();
|
|
if (type) {
|
|
if (strcasecmp(type, "host") == 0) {
|
|
cond.type = RewriteCond::Type::Host;
|
|
} else if (strcasecmp(type, "request") == 0) {
|
|
cond.type = RewriteCond::Type::Request;
|
|
} else {
|
|
throw InvalidArgumentException("rewrite rule",
|
|
"(invalid cond type)");
|
|
}
|
|
} else {
|
|
cond.type = RewriteCond::Type::Request;
|
|
}
|
|
cond.negate = chdf["negate"].getBool(false);
|
|
}
|
|
|
|
}
|
|
|
|
if (vh["IpBlockMap"].firstChild().exists()) {
|
|
Hdf ipblocks = vh["IpBlockMap"];
|
|
m_ipBlocks = IpBlockMapPtr(new IpBlockMap(ipblocks));
|
|
}
|
|
|
|
Hdf logFilters = vh["LogFilters"];
|
|
for (Hdf hdf = logFilters.firstChild(); hdf.exists(); hdf = hdf.next()) {
|
|
QueryStringFilter filter;
|
|
filter.urlPattern = Util::format_pattern(hdf["url"].getString(""), true);
|
|
filter.replaceWith = hdf["value"].getString("");
|
|
filter.replaceWith = "\\1=" + filter.replaceWith;
|
|
|
|
string pattern = hdf["pattern"].getString("");
|
|
vector<string> names;
|
|
hdf["params"].get(names);
|
|
|
|
if (pattern.empty()) {
|
|
for (unsigned int i = 0; i < names.size(); i++) {
|
|
if (pattern.empty()) {
|
|
pattern = "(?<=[&\?])(";
|
|
} else {
|
|
pattern += "|";
|
|
}
|
|
pattern += names[i];
|
|
}
|
|
if (!pattern.empty()) {
|
|
pattern += ")=.*?(?=(&|$))";
|
|
pattern = Util::format_pattern(pattern, false);
|
|
}
|
|
} else if (!names.empty()) {
|
|
throw InvalidArgumentException
|
|
("log filter", "(cannot specify both params and pattern)");
|
|
}
|
|
|
|
filter.namePattern = pattern;
|
|
m_queryStringFilters.push_back(filter);
|
|
}
|
|
|
|
vh["ServerVariables"].get(m_serverVars);
|
|
m_serverName = vh["ServerName"].getString();
|
|
}
|
|
|
|
bool VirtualHost::match(const string &host) const {
|
|
if (!m_pattern.empty()) {
|
|
Variant ret = preg_match(String(m_pattern.c_str(), m_pattern.size(),
|
|
AttachLiteral),
|
|
String(host.c_str(), host.size(),
|
|
AttachLiteral));
|
|
return ret.toInt64() > 0;
|
|
} else if (!m_prefix.empty()) {
|
|
return strncasecmp(host.c_str(), m_prefix.c_str(), m_prefix.size()) == 0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int get_backref(const char **s) {
|
|
assert('0' <= **s && **s <= '9');
|
|
int val = **s - '0';
|
|
*s += 1;
|
|
if ('0' <= **s && **s <= '9') {
|
|
val = val * 10 + **s - '0';
|
|
*s += 1;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
bool VirtualHost::rewriteURL(CStrRef host, String &url, bool &qsa,
|
|
int &redirect) const {
|
|
String normalized = url;
|
|
if (normalized.empty() || normalized.charAt(0) != '/') {
|
|
normalized = String("/") + normalized;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < m_rewriteRules.size(); i++) {
|
|
const RewriteRule &rule = m_rewriteRules[i];
|
|
|
|
bool passed = true;
|
|
for (vector<RewriteCond>::const_iterator it = rule.rewriteConds.begin();
|
|
it != rule.rewriteConds.end(); ++it) {
|
|
String subject;
|
|
if (it->type == RewriteCond::Type::Request) {
|
|
subject = normalized;
|
|
} else {
|
|
subject = host;
|
|
}
|
|
Variant ret = preg_match(String(it->pattern.c_str(), it->pattern.size(),
|
|
AttachLiteral), subject);
|
|
if (!same(ret, it->negate ? 0 : 1)) {
|
|
passed = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!passed) continue;
|
|
Variant matches;
|
|
int count = preg_match(rule.pattern.c_str(),
|
|
normalized,
|
|
matches).toInt64();
|
|
if (count > 0) {
|
|
const char *s = rule.to.c_str();
|
|
StringBuffer ret;
|
|
while (*s) {
|
|
int backref = -1;
|
|
if (*s == '\\') {
|
|
if ('0' <= s[1] && s[1] <= '9') {
|
|
s++;
|
|
backref = get_backref(&s);
|
|
} else if (s[1] == '\\') {
|
|
s++;
|
|
}
|
|
} else if (*s == '$') {
|
|
if (s[1] == '{') {
|
|
const char *t = s+2;
|
|
if ('0' <= *t && *t <= '9') {
|
|
backref = get_backref(&t);
|
|
if (*t != '}') {
|
|
backref = -1;
|
|
} else {
|
|
s = t+1;
|
|
}
|
|
}
|
|
} else if ('0' <= s[1] && s[1] <= '9') {
|
|
s++;
|
|
backref = get_backref(&s);
|
|
}
|
|
}
|
|
if (backref >= 0) {
|
|
String br = matches[backref].toString();
|
|
if (rule.encode_backrefs) {
|
|
br = StringUtil::UrlEncode(br);
|
|
}
|
|
ret.append(br);
|
|
} else {
|
|
ret.append(s, 1);
|
|
s++;
|
|
}
|
|
}
|
|
|
|
url = ret.detach();
|
|
qsa = rule.qsa;
|
|
redirect = rule.redirect;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool VirtualHost::isBlocking(const std::string &command,
|
|
const std::string &ip) const {
|
|
if (!m_ipBlocks) {
|
|
return RuntimeOption::IpBlocks->isBlocking(command, ip);
|
|
}
|
|
return m_ipBlocks->isBlocking(command, ip);
|
|
}
|
|
|
|
std::string VirtualHost::serverName(const std::string &host) const {
|
|
if (!m_serverName.empty()) {
|
|
return m_serverName;
|
|
}
|
|
|
|
if (!RuntimeOption::DefaultServerNameSuffix.empty()) {
|
|
if (!m_pattern.empty()) {
|
|
Variant matches;
|
|
Variant ret = preg_match(String(m_pattern.c_str(), m_pattern.size(),
|
|
AttachLiteral),
|
|
String(host.c_str(), host.size(),
|
|
AttachLiteral),
|
|
matches);
|
|
if (ret.toInt64() > 0) {
|
|
String prefix = matches[1].toString();
|
|
if (prefix.empty()) {
|
|
prefix = matches[0].toString();
|
|
}
|
|
if (!prefix.empty()) {
|
|
return std::string(prefix.data()) +
|
|
RuntimeOption::DefaultServerNameSuffix;
|
|
}
|
|
}
|
|
} else if (!m_prefix.empty()) {
|
|
return m_prefix + RuntimeOption::DefaultServerNameSuffix;
|
|
}
|
|
}
|
|
|
|
return RuntimeOption::Host;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// query string filter
|
|
|
|
std::string VirtualHost::filterUrl(const std::string &url) const {
|
|
assert(!m_queryStringFilters.empty());
|
|
|
|
for (unsigned int i = 0; i < m_queryStringFilters.size(); i++) {
|
|
const QueryStringFilter &filter = m_queryStringFilters[i];
|
|
|
|
bool match = true;
|
|
if (!filter.urlPattern.empty()) {
|
|
Variant ret = preg_match(String(filter.urlPattern.c_str(),
|
|
filter.urlPattern.size(),
|
|
AttachLiteral),
|
|
String(url.c_str(), url.size(),
|
|
AttachLiteral));
|
|
match = (ret.toInt64() > 0);
|
|
}
|
|
|
|
if (match) {
|
|
if (filter.namePattern.empty()) {
|
|
return "";
|
|
}
|
|
|
|
Variant ret;
|
|
int count = preg_replace(ret, filter.namePattern.c_str(),
|
|
String(filter.replaceWith.c_str(),
|
|
filter.replaceWith.size(),
|
|
AttachLiteral),
|
|
String(url.c_str(), url.size(),
|
|
AttachLiteral));
|
|
if (!same(ret, false) && count > 0) {
|
|
return ret.toString().data();
|
|
}
|
|
|
|
return url;
|
|
}
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|