Create dedicate crate for php_escaping.rs
[hiphop-php.git] / hphp / runtime / server / request-uri.cpp
blobca42eedf870bfecc9055c2ea5d5800c668526407
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 +----------------------------------------------------------------------+
16 #include "hphp/runtime/server/request-uri.h"
18 #include <sys/types.h>
19 #include <sys/stat.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"
31 namespace HPHP {
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) {
47 redirectURL += "?";
48 } else {
49 // has query in 404 string
50 redirectURL += "&";
52 redirectURL += m_queryString;
54 if (process(vhost, transport, sourceRoot, pathTranslation,
55 redirectURL.data())) {
56 // 404 redirection succeed
57 return;
60 transport->sendString(getDefault404(), 404);
61 transport->onSendEnd();
62 m_done = true;
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);
76 m_rewritten = 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)) {
83 return false;
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();
96 } else {
97 m_pathInfo = m_origPathInfo;
99 return true;
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;
108 return true;
112 if (!rewriteURL(vhost, transport, pathTranslation, sourceRoot)) {
113 // Redirection
114 m_done = true;
115 return true;
117 if (!resolveURL(vhost, pathTranslation, sourceRoot)) {
118 // Can't find
119 return false;
121 return true;
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, '#');
128 if (fragment) {
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);
134 } else {
135 base = String(url, fragment - url, CopyString);
136 querys = "";
138 } else if (query) {
139 base = String(url, query - url, CopyString);
140 ++query; // skipping ?
141 querys = String(query, CopyString);
142 } else {
143 base = String(url, CopyString);
144 querys = "";
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) {
159 bool qsa = false;
160 int redirect = 0;
161 std::string host = transport->getHeader("host");
162 m_rewrittenURL = m_originalURL;
163 if (vhost->rewriteURL(host, m_rewrittenURL, qsa, redirect)) {
164 m_rewritten = true;
165 if (qsa && !m_queryString.empty()) {
166 m_rewrittenURL += (m_rewrittenURL.find('?') < 0) ? "?" : "&";
167 m_rewrittenURL += m_queryString;
169 if (redirect) {
170 if (m_rewrittenURL.substr(0, 7) != s_http &&
171 m_rewrittenURL.substr(0, 8) != s_https) {
172 PrependSlash(m_rewrittenURL);
174 if (redirect < 0) {
175 std::string error;
176 StringBuffer response;
177 int code = 0;
178 HttpProtocol::ProxyRequest(transport, true,
179 m_rewrittenURL.toCppString(),
180 code, error,
181 response);
182 if (!code) {
183 transport->sendString(error, 500, false, false, "proxyRequest");
184 } else {
185 const char* respData = response.data();
186 if (!respData) respData = "";
187 transport->sendRaw(const_cast<char*>(respData),
188 response.size(), code);
190 transport->onSendEnd();
191 } else {
192 transport->redirect(m_rewrittenURL.c_str(), redirect);
194 return false;
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;
207 if (!url.empty() &&
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();
213 return false;
215 url += "/";
216 m_rewritten = true;
217 String queryStr;
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);
229 return false;
233 return true;
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) {
245 String startURL;
246 if (m_rewritten) {
247 startURL = m_rewrittenURL;
248 } else {
249 startURL = m_originalURL;
251 startURL = FileUtil::canonicalize(startURL.c_str(), startURL.size(), false);
252 m_resolvedURL = startURL;
254 while (!virtualFileExists(vhost, sourceRoot, pathTranslation,
255 m_resolvedURL)) {
256 int pos = m_resolvedURL.rfind('/');
257 if (pos <= 0) {
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,
267 m_resolvedURL)) {
268 m_defaultDoc = true;
269 return true;
271 return false;
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);
285 return true;
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) == '/') {
293 return false;
295 String canon = FileUtil::canonicalize(filename);
296 if (!vhost->getDocumentRoot().empty()) {
297 std::string fullname = canon.data();
298 int i = 0;
299 while (i < fullname.size() && fullname[i] == '/') ++i;
300 if (i) {
301 fullname = fullname.substr(i);
303 if (!i || !m_rewritten) {
304 fullname = pathTranslation + fullname;
306 m_path = fullname;
307 m_absolutePath = String(sourceRoot) + m_path;
308 processExt();
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())) {
315 return true;
318 struct stat st;
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));
326 m_path = canon;
327 m_absolutePath = String(sourceRoot) + canon;
328 processExt();
329 return true;
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);
344 } else {
345 fullname = pathTranslation + fullname;
347 m_path = fullname;
348 m_absolutePath = String(sourceRoot) + m_path;
349 processExt();
351 if (StaticContentCache::TheFileCache && !fullname.empty() &&
352 StaticContentCache::TheFileCache->dirExists(fullname.c_str())) {
353 return true;
356 const std::vector<std::string> &allowedDirectories =
357 VirtualHost::GetAllowedDirectories();
358 if (find(allowedDirectories.begin(),
359 allowedDirectories.end(),
360 fullname.c_str()) != allowedDirectories.end()) {
361 return true;
363 struct stat st;
364 return (stat(m_absolutePath.c_str(), &st) == 0 &&
365 (st.st_mode & S_IFMT) == S_IFDIR);
367 m_path = foldername;
368 m_absolutePath = String(sourceRoot) + foldername;
369 processExt();
370 return true;
373 void RequestURI::processExt() {
374 m_ext = parseExt(m_path);
375 if (RuntimeOption::ForbiddenFileExtensions.empty()) {
376 return;
378 if (m_ext &&
379 RuntimeOption::ForbiddenFileExtensions.find(m_ext) !=
380 RuntimeOption::ForbiddenFileExtensions.end()) {
381 m_forbidden = true;
386 * Parse file extension from a path
388 const char *RequestURI::parseExt(const String& s) {
389 int pos = s.rfind('.');
390 if (pos == -1) {
391 return nullptr;
393 if (s.find('/', pos) != -1) {
394 // '/' after '.' is not extension, e.g., "./foo" "../bar"
395 return nullptr;
397 return s.data() + pos + 1;
400 void RequestURI::PrependSlash(String &s) {
401 if (!s.empty() && s.charAt(0) != '/') {
402 s = String("/") + s;
406 void RequestURI::dump() {
407 m_originalURL.dump();
408 m_queryString.dump();
409 m_rewrittenURL.dump();
410 m_resolvedURL.dump();
411 m_pathInfo.dump();
412 m_origPathInfo.dump();
413 m_absolutePath.dump();
414 m_path.dump();
417 void RequestURI::clear() {
418 m_originalURL.reset();
419 m_queryString.reset();
420 m_rewrittenURL.reset();
421 m_resolvedURL.reset();
422 m_pathInfo.reset();
423 m_origPathInfo.reset();
424 m_absolutePath.reset();
425 m_path.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>";
435 ret += "</ul>";
437 return ret;
440 ///////////////////////////////////////////////////////////////////////////////