Create dedicate crate for php_escaping.rs
[hiphop-php.git] / hphp / runtime / server / virtual-host.cpp
blobaa1e0163a0e385a61a2153aefc0f910ecd521c8f
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/server/virtual-host.h"
19 #include <stdexcept>
21 #include "hphp/runtime/base/comparisons.h"
22 #include "hphp/runtime/base/config.h"
23 #include "hphp/runtime/base/execution-context.h"
24 #include "hphp/runtime/base/preg.h"
25 #include "hphp/runtime/base/runtime-option.h"
26 #include "hphp/runtime/base/string-util.h"
27 #include "hphp/runtime/base/type-string.h"
28 #include "hphp/runtime/base/variable-serializer.h"
29 #include "hphp/util/logger.h"
30 #include "hphp/util/text-util.h"
32 namespace HPHP {
33 ///////////////////////////////////////////////////////////////////////////////
35 bool VirtualHost::IsDefault(const IniSetting::Map& /*ini*/, const Hdf& vh,
36 const std::string& ini_key /* = "" */) {
37 if (vh.exists() && !vh.isEmpty()) {
38 return (vh.getName() == "default");
39 } else {
40 return (ini_key == "default");
44 VirtualHost &VirtualHost::GetDefault() {
45 // VirtualHost acquires global mutexes in its constructor, so we allocate
46 // s_default_vhost lazily to ensure that all of the global mutexes have
47 // been initialized before we enter the constructor.
48 static VirtualHost s_default_vhost;
49 return s_default_vhost;
52 void VirtualHost::SetCurrent(VirtualHost *vhost) {
53 g_context->setVirtualHost(vhost ? vhost : &VirtualHost::GetDefault());
54 UpdateSerializationSizeLimit();
57 const VirtualHost *VirtualHost::GetCurrent() {
58 const VirtualHost *ret = g_context.isNull() ?
59 nullptr : g_context->getVirtualHost();
60 if (!ret) ret = &VirtualHost::GetDefault();
61 return ret;
64 VirtualHost* VirtualHost::Resolve(const std::string& host) {
65 auto const hostString = String(host.c_str(), host.size(), CopyString);
66 for (auto vhost : RuntimeOption::VirtualHosts) {
67 if (vhost->match(hostString)) {
68 return vhost.get();
71 return nullptr;
74 int64_t VirtualHost::getMaxPostSize() const {
75 if (m_runtimeOption.maxPostSize != -1) {
76 return m_runtimeOption.maxPostSize;
78 return RuntimeOption::MaxPostSize;
81 int64_t VirtualHost::GetLowestMaxPostSize() {
82 auto lowest = RuntimeOption::MaxPostSize;
83 for (auto vhost : RuntimeOption::VirtualHosts) {
84 auto max = vhost->getMaxPostSize();
85 lowest = std::min(lowest, max);
87 return lowest;
90 int64_t VirtualHost::GetMaxPostSize() {
91 const VirtualHost *vh = GetCurrent();
92 assertx(vh);
93 return vh->getMaxPostSize();
96 int64_t VirtualHost::GetUploadMaxFileSize() {
97 const VirtualHost *vh = GetCurrent();
98 assertx(vh);
99 if (vh->m_runtimeOption.uploadMaxFileSize != -1) {
100 return vh->m_runtimeOption.uploadMaxFileSize;
102 return RuntimeOption::UploadMaxFileSize;
105 void VirtualHost::UpdateSerializationSizeLimit() {
106 const VirtualHost *vh = GetCurrent();
107 assertx(vh);
108 if (vh->m_runtimeOption.serializationSizeLimit != StringData::MaxSize) {
109 VariableSerializer::serializationSizeLimit->value =
110 vh->m_runtimeOption.serializationSizeLimit;
114 bool VirtualHost::alwaysDecodePostData(const String& origPath) const {
115 if (!m_alwaysDecodePostData) return false;
116 if (m_decodePostDataBlackList.empty()) return true;
117 return !m_decodePostDataBlackList.count(origPath.toCppString());
120 const std::vector<std::string> &VirtualHost::GetAllowedDirectories() {
121 const VirtualHost *vh = GetCurrent();
122 assertx(vh);
123 if (!vh->m_runtimeOption.allowedDirectories.empty()) {
124 return vh->m_runtimeOption.allowedDirectories;
126 return RuntimeOption::AllowedDirectories;
129 void VirtualHost::SortAllowedDirectories(std::vector<std::string>& dirs) {
131 Make sure corresponding realpath's are also allowed
133 for (unsigned int i = 0, s = dirs.size(); i < s; i++) {
134 std::string &directory = dirs[i];
135 char resolved_path[PATH_MAX];
136 if (realpath(directory.c_str(), resolved_path) &&
137 directory != resolved_path) {
138 dirs.push_back(resolved_path);
142 sort so we can use upper_bound to find the right prefix,
143 rather than using a linear scan (and so we can remove
144 duplicates, etc below)
146 std::sort(dirs.begin(), dirs.end());
148 AllowedDirectories is a list of prefixes, so if x is a substring
149 of y, we dont need y (also remove any duplicates).
151 dirs.erase(std::unique(dirs.begin(), dirs.end(),
152 [](const std::string &a, const std::string &b) {
153 if (a.size() < b.size()) {
154 return !b.compare(0, a.size(), a);
156 return !a.compare(0, b.size(), b);
158 dirs.end());
161 ///////////////////////////////////////////////////////////////////////////////
163 void VirtualHost::initRuntimeOption(const IniSetting::Map& ini, const Hdf& vh) {
164 int requestTimeoutSeconds =
165 Config::GetInt32(ini, vh, "overwrite.Server.RequestTimeoutSeconds", -1,
166 false);
167 int64_t maxPostSize =
168 Config::GetInt32(ini, vh, "overwrite.Server.MaxPostSize", -1, false);
169 if (maxPostSize != -1) maxPostSize *= (1LL << 20);
170 int64_t uploadMaxFileSize =
171 Config::GetInt32(ini, vh, "overwrite.Server.Upload.UploadMaxFileSize", -1,
172 false);
173 if (uploadMaxFileSize != -1) uploadMaxFileSize *= (1LL << 20);
174 int64_t serializationSizeLimit =
175 Config::GetInt32(
176 ini,
177 vh, "overwrite.ResourceLimit.SerializationSizeLimit",
178 StringData::MaxSize, false);
179 m_runtimeOption.allowedDirectories = Config::GetStrVector(
180 ini,
181 vh, "overwrite.Server.AllowedDirectories",
182 m_runtimeOption.allowedDirectories, false);
183 m_runtimeOption.requestTimeoutSeconds = requestTimeoutSeconds;
184 m_runtimeOption.maxPostSize = maxPostSize;
185 m_runtimeOption.uploadMaxFileSize = uploadMaxFileSize;
186 m_runtimeOption.serializationSizeLimit = serializationSizeLimit;
188 m_documentRoot = RuntimeOption::SourceRoot + m_pathTranslation;
189 if (m_documentRoot.length() > 1 &&
190 m_documentRoot.back() == '/') {
191 m_documentRoot.pop_back();
192 // Make sure we've not converted "/" to "" (which is why we're checking
193 // length() > 1 instead of !empty() above)
194 assertx(!m_documentRoot.empty());
198 void VirtualHost::addAllowedDirectories(const std::vector<std::string>& dirs) {
199 if (!m_runtimeOption.allowedDirectories.empty()) {
200 m_runtimeOption.allowedDirectories.insert(
201 m_runtimeOption.allowedDirectories.end(),
202 dirs.begin(), dirs.end());
203 SortAllowedDirectories(m_runtimeOption.allowedDirectories);
207 int VirtualHost::getRequestTimeoutSeconds(int defaultTimeout) const {
208 return m_runtimeOption.requestTimeoutSeconds < 0 ?
209 defaultTimeout : m_runtimeOption.requestTimeoutSeconds;
212 VirtualHost::VirtualHost() : m_disabled(false) {
213 IniSetting::Map ini = IniSetting::Map::object;
214 Hdf empty;
215 // Pass empty config maps; the default values are being used
216 // for these runtime option values / overwrites.
217 initRuntimeOption(ini, empty);
220 VirtualHost::VirtualHost(const IniSetting::Map& ini, const Hdf& vh,
221 const std::string &ini_key /* = "" */
222 ) : m_disabled(false) {
223 init(ini, vh, ini_key);
226 void VirtualHost::init(const IniSetting::Map& ini, const Hdf& vh,
227 const std::string& ini_key /* = "" */) {
228 m_name = vh.exists() && !vh.isEmpty() ? vh.getName() : ini_key;
230 m_prefix = Config::GetString(ini, vh, "Prefix", "", false);
231 auto pattern = format_pattern(Config::GetString(ini, vh, "Pattern", "",
232 false), false);
233 m_pathTranslation = Config::GetString(ini, vh, "PathTranslation", "",
234 false);
235 if (!pattern.empty()) {
236 pattern += "i"; // case-insensitive
237 m_pattern = makeStaticString(pattern);
240 if (!m_pathTranslation.empty() &&
241 m_pathTranslation[m_pathTranslation.length() - 1] != '/') {
242 m_pathTranslation += '/';
245 initRuntimeOption(ini, vh); // overwrites
247 m_disabled = Config::GetBool(ini, vh, "Disabled", false, false);
249 m_checkExistenceBeforeRewrite =
250 Config::GetBool(ini, vh, "CheckExistenceBeforeRewrite", true, false);
252 m_alwaysDecodePostData = Config::GetBool(
253 ini,
255 "AlwaysDecodePostData",
256 RuntimeOption::AlwaysDecodePostDataDefault,
257 false);
259 m_decodePostDataBlackList =
260 Config::GetSetC(ini, vh, "DecodePostDataBlackList");
262 auto rr_callback = [&](const IniSetting::Map& ini_rr, const Hdf& hdf_rr,
263 const std::string& /*ini_rr_key*/) {
264 RewriteRule dummy;
265 m_rewriteRules.push_back(dummy);
266 RewriteRule &rule = m_rewriteRules.back();
267 rule.pattern = makeStaticString(format_pattern(
268 Config::GetString(ini_rr, hdf_rr, "pattern", "", false),
269 true));
270 rule.to = Config::GetString(ini_rr, hdf_rr, "to", "", false);
271 rule.qsa = Config::GetBool(ini_rr, hdf_rr, "qsa", false, false);
272 rule.redirect = Config::GetInt16(ini_rr, hdf_rr, "redirect", 0, false);
273 rule.encode_backrefs = Config::GetBool(ini_rr, hdf_rr, "encode_backrefs",
274 false, false);
276 if (rule.pattern->empty() || rule.to.empty()) {
277 throw std::runtime_error("Invalid rewrite rule: (empty pattern or to)");
280 auto rc_callback = [&](const IniSetting::Map& ini_rc, const Hdf& hdf_rc,
281 const std::string& /*ini_rc_key*/) {
282 RewriteCond dummy;
283 rule.rewriteConds.push_back(dummy);
284 RewriteCond &cond = rule.rewriteConds.back();
285 cond.pattern = makeStaticString(format_pattern(
286 Config::GetString(ini_rc, hdf_rc, "pattern", "", false), true));
287 if (cond.pattern->empty()) {
288 throw std::runtime_error("Invalid rewrite rule: (empty cond pattern)");
290 std::string type = Config::GetString(ini_rc, hdf_rc, "type", "", false);
291 if (!type.empty()) {
292 if (strcasecmp(type.c_str(), "host") == 0) {
293 cond.type = RewriteCond::Type::Host;
294 } else if (strcasecmp(type.c_str(), "request") == 0) {
295 cond.type = RewriteCond::Type::Request;
296 } else {
297 throw std::runtime_error("Invalid rewrite rule: (invalid "
298 "cond type)");
300 } else {
301 cond.type = RewriteCond::Type::Request;
303 cond.negate = Config::GetBool(ini_rc, hdf_rc, "negate", false, false);
305 Config::Iterate(rc_callback, ini_rr, hdf_rr, "conditions", false);
307 Config::Iterate(rr_callback, ini, vh, "RewriteRules", false);
309 m_ipBlocks = std::make_shared<IpBlockMap>(ini, vh);
311 auto lf_callback = [&](const IniSetting::Map& ini_lf, const Hdf& hdf_lf,
312 const std::string& /*ini_lf_key*/) {
313 QueryStringFilter filter;
314 filter.urlPattern = format_pattern(Config::GetString(ini_lf, hdf_lf, "url",
315 "", false),
316 true);
317 filter.replaceWith = Config::GetString(ini_lf, hdf_lf, "value", "", false);
318 filter.replaceWith = "\\1=" + filter.replaceWith;
320 std::string namePattern = Config::GetString(ini_lf, hdf_lf, "pattern", "",
321 false);
322 std::vector<std::string> names;
323 names = Config::GetStrVector(ini_lf, hdf_lf, "params", names, false);
325 if (namePattern.empty()) {
326 for (unsigned int i = 0; i < names.size(); i++) {
327 if (namePattern.empty()) {
328 namePattern = "(?<=[&\?])(";
329 } else {
330 namePattern += "|";
332 namePattern += names[i];
334 if (!namePattern.empty()) {
335 namePattern += ")=.*?(?=(&|$))";
336 namePattern = format_pattern(namePattern, false);
338 } else if (!names.empty()) {
339 throw std::runtime_error("Invalid log filter: (cannot specify "
340 "both params and pattern)");
343 filter.namePattern = namePattern;
344 m_queryStringFilters.push_back(filter);
346 Config::Iterate(lf_callback, ini, vh, "LogFilters", false);
348 m_serverVars = Config::GetMap(ini, vh, "ServerVariables", m_serverVars,
349 false);
350 m_serverName = Config::GetString(ini, vh, "ServerName", m_serverName, false);
352 // We don't require a prefix or pattern for the default
353 if (m_name != "default" && !valid()) {
354 throw std::runtime_error("virtual host missing prefix or pattern");
358 bool VirtualHost::match(const String &host) const {
359 if (m_pattern) {
360 Variant ret = preg_match(m_pattern, host.get());
361 return ret.toInt64() > 0;
362 } else if (!m_prefix.empty()) {
363 return strncasecmp(host.c_str(), m_prefix.c_str(), m_prefix.size()) == 0;
365 return true;
368 static int get_backref(const char **s) {
369 assertx('0' <= **s && **s <= '9');
370 int val = **s - '0';
371 *s += 1;
372 if ('0' <= **s && **s <= '9') {
373 val = val * 10 + **s - '0';
374 *s += 1;
376 return val;
379 bool VirtualHost::rewriteURL(const String& host, String &url, bool &qsa,
380 int &redirect) const {
381 String normalized = url;
382 if (normalized.empty() || normalized.charAt(0) != '/') {
383 normalized = String("/") + normalized;
386 Logger::Verbose("Matching host:%s url:%s using vhost:%s",
387 host.c_str(), url.c_str(), m_name.c_str());
389 for (unsigned int i = 0; i < m_rewriteRules.size(); i++) {
390 const RewriteRule &rule = m_rewriteRules[i];
392 bool passed = true;
393 for (auto it = rule.rewriteConds.begin();
394 it != rule.rewriteConds.end(); ++it) {
395 String subject;
396 if (it->type == RewriteCond::Type::Request) {
397 subject = normalized;
398 } else {
399 subject = host;
401 Variant ret = preg_match(it->pattern, subject.get());
402 if (!same(ret, it->negate ? 0 : 1)) {
403 passed = false;
404 break;
407 if (!passed) continue;
408 Variant matches;
409 int count = preg_match(rule.pattern,
410 normalized.get(),
411 &matches).toInt64();
412 if (count > 0) {
413 Logger::Verbose("Matched pattern %s", rule.pattern->data());
415 const char *s = rule.to.c_str();
416 StringBuffer ret;
417 while (*s) {
418 int backref = -1;
419 if (*s == '\\') {
420 if ('0' <= s[1] && s[1] <= '9') {
421 s++;
422 backref = get_backref(&s);
423 } else if (s[1] == '\\') {
424 s++;
426 } else if (*s == '$') {
427 if (s[1] == '{') {
428 const char *t = s+2;
429 if ('0' <= *t && *t <= '9') {
430 backref = get_backref(&t);
431 if (*t != '}') {
432 backref = -1;
433 } else {
434 s = t+1;
437 } else if ('0' <= s[1] && s[1] <= '9') {
438 s++;
439 backref = get_backref(&s);
442 if (backref >= 0) {
443 String br = matches.toArray()[backref].toString();
444 if (rule.encode_backrefs) {
445 br = StringUtil::UrlEncode(br);
447 ret.append(br);
448 } else {
449 ret.append(s, 1);
450 s++;
454 url = ret.detach();
455 qsa = rule.qsa;
456 redirect = rule.redirect;
457 return true;
458 } else {
459 Logger::Verbose("Did not match pattern %s", rule.pattern->data());
462 return false;
465 bool VirtualHost::isBlocking(const std::string &command,
466 const std::string &ip) const {
467 if (!m_ipBlocks) {
468 return RuntimeOption::IpBlocks->isBlocking(command, ip);
470 return m_ipBlocks->isBlocking(command, ip);
473 std::string VirtualHost::serverName(const std::string &host) const {
474 if (!m_serverName.empty()) {
475 return m_serverName;
478 if (!RuntimeOption::DefaultServerNameSuffix.empty()) {
479 if (m_pattern) {
480 Variant matches;
481 Variant ret = preg_match(m_pattern,
482 String(host.c_str(), host.size(),
483 CopyString).get(),
484 &matches);
485 if (ret.toInt64() > 0) {
486 String prefix = matches.toArray()[1].toString();
487 if (prefix.empty()) {
488 prefix = matches.toArray()[0].toString();
490 if (!prefix.empty()) {
491 return std::string(prefix.data()) +
492 RuntimeOption::DefaultServerNameSuffix;
495 } else if (!m_prefix.empty()) {
496 return m_prefix + RuntimeOption::DefaultServerNameSuffix;
500 return RuntimeOption::Host;
503 ///////////////////////////////////////////////////////////////////////////////
504 // query string filter
506 std::string VirtualHost::filterUrl(const std::string &url) const {
507 assertx(!m_queryStringFilters.empty());
509 for (unsigned int i = 0; i < m_queryStringFilters.size(); i++) {
510 const QueryStringFilter &filter = m_queryStringFilters[i];
512 bool match = true;
513 if (!filter.urlPattern.empty()) {
514 Variant ret = preg_match(String(filter.urlPattern.c_str(),
515 filter.urlPattern.size(),
516 CopyString),
517 String(url.c_str(), url.size(),
518 CopyString));
519 match = (ret.toInt64() > 0);
522 if (match) {
523 if (filter.namePattern.empty()) {
524 return "";
527 Variant ret;
528 int count = preg_replace(ret, filter.namePattern.c_str(),
529 String(filter.replaceWith.c_str(),
530 filter.replaceWith.size(),
531 CopyString),
532 String(url.c_str(), url.size(),
533 CopyString));
534 if (!same(ret, false) && count > 0) {
535 return ret.toString().data();
538 return url;
542 return url;
545 ///////////////////////////////////////////////////////////////////////////////