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 +----------------------------------------------------------------------+
16 #include "hphp/runtime/server/request-uri.h"
18 #include <sys/types.h>
21 #include <folly/portability/Unistd.h>
23 #include "hphp/runtime/base/file-util.h"
24 #include "hphp/runtime/base/runtime-option.h"
25 #include "hphp/runtime/base/string-util.h"
26 #include "hphp/runtime/server/http-protocol.h"
27 #include "hphp/runtime/server/static-content-cache.h"
28 #include "hphp/runtime/server/transport.h"
29 #include "hphp/runtime/server/virtual-host.h"
32 ///////////////////////////////////////////////////////////////////////////////
34 RequestURI::RequestURI(const VirtualHost
*vhost
, Transport
*transport
,
35 const std::string
&pathTranslation
,
36 const std::string
&sourceRoot
)
37 : m_rewritten(false), m_defaultDoc(false), m_done(false),
38 m_forbidden(false), m_ext(nullptr) {
39 if (!process(vhost
, transport
, sourceRoot
, pathTranslation
,
40 transport
->getServerObject()) ||
41 (m_forbidden
&& RuntimeOption::ForbiddenAs404
)) {
42 m_forbidden
= false; // put down forbidden flag since we are redirecting
43 if (!RuntimeOption::ErrorDocument404
.empty()) {
44 String
redirectURL(RuntimeOption::ErrorDocument404
);
45 if (!m_queryString
.empty()) {
46 if (redirectURL
.find('?') == -1) {
49 // has query in 404 string
52 redirectURL
+= m_queryString
;
54 if (process(vhost
, transport
, sourceRoot
, pathTranslation
,
55 redirectURL
.data())) {
56 // 404 redirection succeed
60 transport
->sendString(getDefault404(), 404);
61 transport
->onSendEnd();
66 RequestURI::RequestURI(const std::string
& rpcFunc
)
67 : m_rewritten(false), m_defaultDoc(false), m_done(false) {
68 m_originalURL
= m_rewrittenURL
= m_resolvedURL
= String(rpcFunc
);
71 bool RequestURI::process(const VirtualHost
*vhost
, Transport
*transport
,
72 const std::string
&sourceRoot
,
73 const std::string
&pathTranslation
, const char *url
) {
74 splitURL(url
, m_originalURL
, m_queryString
);
75 m_originalURL
= StringUtil::UrlDecode(m_originalURL
, false);
78 auto scriptFilename
= transport
->getScriptFilename();
79 if (!scriptFilename
.empty()) {
80 // The transport is overriding everything and just handing us the filename
81 m_originalURL
= scriptFilename
;
82 if (!resolveURL(vhost
, pathTranslation
, sourceRoot
)) {
85 if (m_origPathInfo
.empty()) {
86 // PATH_INFO wasn't filled by resolveURL() because m_originalURL
87 // didn't contain it. We set it now, based on PATH_TRANSLATED.
88 m_origPathInfo
= transport
->getPathTranslated();
89 if (!m_origPathInfo
.empty() &&
90 m_origPathInfo
.charAt(0) != '/') {
91 m_origPathInfo
= "/" + m_origPathInfo
;
94 if (transport
->isPathInfoSet()) {
95 m_pathInfo
=transport
->getPathInfo();
97 m_pathInfo
= m_origPathInfo
;
102 // Fast path for files that exist
103 if (vhost
->checkExistenceBeforeRewrite()) {
104 String canon
= FileUtil::canonicalize(m_originalURL
);
105 if (virtualFileExists(vhost
, sourceRoot
, pathTranslation
, canon
)) {
106 m_rewrittenURL
= canon
;
107 m_resolvedURL
= canon
;
112 if (!rewriteURL(vhost
, transport
, pathTranslation
, sourceRoot
)) {
117 if (!resolveURL(vhost
, pathTranslation
, sourceRoot
)) {
124 void RequestURI::splitURL(String surl
, String
&base
, String
&querys
) {
125 const char *url
= surl
.c_str();
126 const char *query
= strchr(url
, '?');
127 const char *fragment
= strchr(url
, '#');
129 // ignore everything after the #
130 if (query
&& fragment
> query
) {
131 base
= String(url
, query
- url
, CopyString
);
132 ++query
; // skipping ?
133 querys
= String(query
, fragment
- query
, CopyString
);
135 base
= String(url
, fragment
- url
, CopyString
);
139 base
= String(url
, query
- url
, CopyString
);
140 ++query
; // skipping ?
141 querys
= String(query
, CopyString
);
143 base
= String(url
, CopyString
);
148 const StaticString
s_http("http://");
149 const StaticString
s_https("https://");
152 * Precondition: m_originalURL and m_queryString are set
153 * Postcondition: Output is false and we are redirecting OR
154 * m_rewrittenURL is set and m_queryString is updated if needed
156 bool RequestURI::rewriteURL(const VirtualHost
*vhost
, Transport
*transport
,
157 const std::string
&pathTranslation
,
158 const std::string
&sourceRoot
) {
161 std::string host
= transport
->getHeader("host");
162 m_rewrittenURL
= m_originalURL
;
163 if (vhost
->rewriteURL(host
, m_rewrittenURL
, qsa
, redirect
)) {
165 if (qsa
&& !m_queryString
.empty()) {
166 m_rewrittenURL
+= (m_rewrittenURL
.find('?') < 0) ? "?" : "&";
167 m_rewrittenURL
+= m_queryString
;
170 if (m_rewrittenURL
.substr(0, 7) != s_http
&&
171 m_rewrittenURL
.substr(0, 8) != s_https
) {
172 PrependSlash(m_rewrittenURL
);
176 StringBuffer response
;
178 HttpProtocol::ProxyRequest(transport
, true,
179 m_rewrittenURL
.toCppString(),
183 transport
->sendString(error
, 500, false, false, "proxyRequest");
185 const char* respData
= response
.data();
186 if (!respData
) respData
= "";
187 transport
->sendRaw(const_cast<char*>(respData
),
188 response
.size(), code
);
190 transport
->onSendEnd();
192 transport
->redirect(m_rewrittenURL
.c_str(), redirect
);
196 splitURL(m_rewrittenURL
, m_rewrittenURL
, m_queryString
);
198 m_rewrittenURL
= FileUtil::canonicalize(m_rewrittenURL
);
199 if (!m_rewritten
&& m_rewrittenURL
.charAt(0) == '/') {
200 // A un-rewritten URL is always relative, so remove prepending /
201 m_rewrittenURL
= m_rewrittenURL
.substr(1);
204 // If the URL refers to a folder but does not end
205 // with a slash, then we need to redictect
206 String url
= m_rewrittenURL
;
208 url
.charAt(url
.length() - 1) != '/') {
209 if (virtualFolderExists(vhost
, sourceRoot
, pathTranslation
, url
)) {
210 if (m_originalURL
.find("..") != String::npos
) {
211 transport
->sendString(getDefault404(), 404);
212 transport
->onSendEnd();
218 m_rewrittenURL
= m_originalURL
;
219 m_rewrittenURL
+= "/";
220 if (!m_queryString
.empty()) {
221 m_rewrittenURL
+= "?";
222 m_rewrittenURL
+= m_queryString
;
224 if (m_rewrittenURL
.substr(0, 7) != s_http
&&
225 m_rewrittenURL
.substr(0, 8) != s_https
) {
226 PrependSlash(m_rewrittenURL
);
228 transport
->redirect(m_rewrittenURL
.c_str(), 301);
237 * Precondition: m_rewrittenURL is set
238 * Postcondition: Output is true and m_path and m_absolutePath are set OR
239 * output is false and no file was found
241 bool RequestURI::resolveURL(const VirtualHost
*vhost
,
242 const std::string
&pathTranslation
,
243 const std::string
&sourceRoot
) {
247 startURL
= m_rewrittenURL
;
249 startURL
= m_originalURL
;
251 startURL
= FileUtil::canonicalize(startURL
.c_str(), startURL
.size(), false);
252 m_resolvedURL
= startURL
;
254 while (!virtualFileExists(vhost
, sourceRoot
, pathTranslation
,
256 int pos
= m_resolvedURL
.rfind('/');
258 // when none of the <subpath> exists, we give up, and try default doc
259 m_resolvedURL
= startURL
;
260 if (!m_resolvedURL
.empty() &&
261 m_resolvedURL
.charAt(m_resolvedURL
.length() - 1) != '/') {
262 m_resolvedURL
+= "/";
264 m_resolvedURL
+= String(RuntimeOption::DefaultDocument
);
265 m_origPathInfo
.reset();
266 if (virtualFileExists(vhost
, sourceRoot
, pathTranslation
,
273 m_resolvedURL
= startURL
.substr(0, pos
);
274 m_origPathInfo
= startURL
.substr(pos
);
276 if (!m_resolvedURL
.empty() &&
277 m_resolvedURL
.charAt(0) != '/') {
278 m_resolvedURL
= "/" + m_resolvedURL
;
280 if (!m_originalURL
.empty() &&
281 m_originalURL
.charAt(0) != '/') {
282 m_originalURL
= "/" + m_originalURL
;
284 m_pathInfo
= FileUtil::canonicalize(m_origPathInfo
);
288 bool RequestURI::virtualFileExists(const VirtualHost
*vhost
,
289 const std::string
&sourceRoot
,
290 const std::string
&pathTranslation
,
291 const String
& filename
) {
292 if (filename
.empty() || filename
.charAt(filename
.length() - 1) == '/') {
295 String canon
= FileUtil::canonicalize(filename
);
296 if (!vhost
->getDocumentRoot().empty()) {
297 std::string fullname
= canon
.data();
299 while (i
< fullname
.size() && fullname
[i
] == '/') ++i
;
301 fullname
= fullname
.substr(i
);
303 if (!i
|| !m_rewritten
) {
304 fullname
= pathTranslation
+ fullname
;
307 m_absolutePath
= String(sourceRoot
) + m_path
;
309 if (RuntimeOption::PathDebug
) {
310 m_triedURLs
.push_back(m_absolutePath
.toCppString());
313 if (StaticContentCache::TheFileCache
&& !fullname
.empty() &&
314 StaticContentCache::TheFileCache
->fileExists(fullname
.c_str())) {
319 return RuntimeOption::AllowedFiles
.find(fullname
.c_str()) !=
320 RuntimeOption::AllowedFiles
.end() ||
321 ((!RuntimeOption::RepoAuthoritative
||
322 RuntimeOption::EnableStaticContentFromDisk
) &&
323 (stat(m_absolutePath
.c_str(), &st
) == 0 &&
324 (st
.st_mode
& S_IFMT
) == S_IFREG
));
327 m_absolutePath
= String(sourceRoot
) + canon
;
332 bool RequestURI::virtualFolderExists(const VirtualHost
*vhost
,
333 const std::string
&sourceRoot
,
334 const std::string
&pathTranslation
,
335 const String
& foldername
) {
336 if (!vhost
->getDocumentRoot().empty()) {
337 std::string fullname
= foldername
.data();
338 // If there is a trailing slash, remove it
339 if (fullname
.size() > 0 && fullname
[fullname
.size()-1] == '/') {
340 fullname
= fullname
.substr(fullname
.size()-1);
342 if (fullname
[0] == '/') {
343 fullname
= fullname
.substr(1);
345 fullname
= pathTranslation
+ fullname
;
348 m_absolutePath
= String(sourceRoot
) + m_path
;
351 if (StaticContentCache::TheFileCache
&& !fullname
.empty() &&
352 StaticContentCache::TheFileCache
->dirExists(fullname
.c_str())) {
356 const std::vector
<std::string
> &allowedDirectories
=
357 VirtualHost::GetAllowedDirectories();
358 if (find(allowedDirectories
.begin(),
359 allowedDirectories
.end(),
360 fullname
.c_str()) != allowedDirectories
.end()) {
364 return (stat(m_absolutePath
.c_str(), &st
) == 0 &&
365 (st
.st_mode
& S_IFMT
) == S_IFDIR
);
368 m_absolutePath
= String(sourceRoot
) + foldername
;
373 void RequestURI::processExt() {
374 m_ext
= parseExt(m_path
);
375 if (RuntimeOption::ForbiddenFileExtensions
.empty()) {
379 RuntimeOption::ForbiddenFileExtensions
.find(m_ext
) !=
380 RuntimeOption::ForbiddenFileExtensions
.end()) {
386 * Parse file extension from a path
388 const char *RequestURI::parseExt(const String
& s
) {
389 int pos
= s
.rfind('.');
393 if (s
.find('/', pos
) != -1) {
394 // '/' after '.' is not extension, e.g., "./foo" "../bar"
397 return s
.data() + pos
+ 1;
400 void RequestURI::PrependSlash(String
&s
) {
401 if (!s
.empty() && s
.charAt(0) != '/') {
406 void RequestURI::dump() {
407 m_originalURL
.dump();
408 m_queryString
.dump();
409 m_rewrittenURL
.dump();
410 m_resolvedURL
.dump();
412 m_origPathInfo
.dump();
413 m_absolutePath
.dump();
417 void RequestURI::clear() {
418 m_originalURL
.reset();
419 m_queryString
.reset();
420 m_rewrittenURL
.reset();
421 m_resolvedURL
.reset();
423 m_origPathInfo
.reset();
424 m_absolutePath
.reset();
428 const std::string
RequestURI::getDefault404() {
429 std::string ret
= "404 File Not Found";
430 if (RuntimeOption::PathDebug
) {
431 ret
+= "<br/>Paths examined:<ul>";
432 for (auto& url
: m_triedURLs
) {
433 ret
+= "<li>" + url
+ "</li>";
440 ///////////////////////////////////////////////////////////////////////////////