2 +----------------------------------------------------------------------+
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"
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"
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");
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();
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
)) {
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
);
90 int64_t VirtualHost::GetMaxPostSize() {
91 const VirtualHost
*vh
= GetCurrent();
93 return vh
->getMaxPostSize();
96 int64_t VirtualHost::GetUploadMaxFileSize() {
97 const VirtualHost
*vh
= GetCurrent();
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();
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();
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
);
161 ///////////////////////////////////////////////////////////////////////////////
163 void VirtualHost::initRuntimeOption(const IniSetting::Map
& ini
, const Hdf
& vh
) {
164 int requestTimeoutSeconds
=
165 Config::GetInt32(ini
, vh
, "overwrite.Server.RequestTimeoutSeconds", -1,
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,
173 if (uploadMaxFileSize
!= -1) uploadMaxFileSize
*= (1LL << 20);
174 int64_t serializationSizeLimit
=
177 vh
, "overwrite.ResourceLimit.SerializationSizeLimit",
178 StringData::MaxSize
, false);
179 m_runtimeOption
.allowedDirectories
= Config::GetStrVector(
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
;
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", "",
233 m_pathTranslation
= Config::GetString(ini
, vh
, "PathTranslation", "",
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(
255 "AlwaysDecodePostData",
256 RuntimeOption::AlwaysDecodePostDataDefault
,
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*/) {
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),
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",
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*/) {
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);
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
;
297 throw std::runtime_error("Invalid rewrite rule: (invalid "
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",
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", "",
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
= "(?<=[&\?])(";
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
,
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 {
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;
368 static int get_backref(const char **s
) {
369 assertx('0' <= **s
&& **s
<= '9');
372 if ('0' <= **s
&& **s
<= '9') {
373 val
= val
* 10 + **s
- '0';
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
];
393 for (auto it
= rule
.rewriteConds
.begin();
394 it
!= rule
.rewriteConds
.end(); ++it
) {
396 if (it
->type
== RewriteCond::Type::Request
) {
397 subject
= normalized
;
401 Variant ret
= preg_match(it
->pattern
, subject
.get());
402 if (!same(ret
, it
->negate
? 0 : 1)) {
407 if (!passed
) continue;
409 int count
= preg_match(rule
.pattern
,
413 Logger::Verbose("Matched pattern %s", rule
.pattern
->data());
415 const char *s
= rule
.to
.c_str();
420 if ('0' <= s
[1] && s
[1] <= '9') {
422 backref
= get_backref(&s
);
423 } else if (s
[1] == '\\') {
426 } else if (*s
== '$') {
429 if ('0' <= *t
&& *t
<= '9') {
430 backref
= get_backref(&t
);
437 } else if ('0' <= s
[1] && s
[1] <= '9') {
439 backref
= get_backref(&s
);
443 String br
= matches
.toArray()[backref
].toString();
444 if (rule
.encode_backrefs
) {
445 br
= StringUtil::UrlEncode(br
);
456 redirect
= rule
.redirect
;
459 Logger::Verbose("Did not match pattern %s", rule
.pattern
->data());
465 bool VirtualHost::isBlocking(const std::string
&command
,
466 const std::string
&ip
) const {
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()) {
478 if (!RuntimeOption::DefaultServerNameSuffix
.empty()) {
481 Variant ret
= preg_match(m_pattern
,
482 String(host
.c_str(), host
.size(),
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
];
513 if (!filter
.urlPattern
.empty()) {
514 Variant ret
= preg_match(String(filter
.urlPattern
.c_str(),
515 filter
.urlPattern
.size(),
517 String(url
.c_str(), url
.size(),
519 match
= (ret
.toInt64() > 0);
523 if (filter
.namePattern
.empty()) {
528 int count
= preg_replace(ret
, filter
.namePattern
.c_str(),
529 String(filter
.replaceWith
.c_str(),
530 filter
.replaceWith
.size(),
532 String(url
.c_str(), url
.size(),
534 if (!same(ret
, false) && count
> 0) {
535 return ret
.toString().data();
545 ///////////////////////////////////////////////////////////////////////////////