/* * Phusion Passenger - http://www.modrails.com/ * Copyright (c) 2010 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef _PASSENGER_DIRECTORY_MAPPER_H_ #define _PASSENGER_DIRECTORY_MAPPER_H_ #include #include #include #include #include "Configuration.hpp" #include "Utils.h" #include "Utils/CachedFileStat.hpp" // The Apache/APR headers *must* come after the Boost headers, otherwise // compilation will fail on OpenBSD. #include #include namespace Passenger { using namespace std; using namespace oxt; /** * Utility class for determining URI-to-application directory mappings. * Given a URI, it will determine whether that URI belongs to a Phusion * Passenger-handled application, what the base URI of that application is, * and what the associated 'public' directory is. * * @note This class is not thread-safe, but is reentrant. * @ingroup Core */ class DirectoryMapper { public: enum ApplicationType { NONE, RAILS, RACK, WSGI }; private: DirConfig *config; request_rec *r; CachedFileStat *cstat; unsigned int throttleRate; bool baseURIKnown; const char *baseURI; ApplicationType appType; inline bool shouldAutoDetectRails() { return config->autoDetectRails == DirConfig::ENABLED || config->autoDetectRails == DirConfig::UNSET; } inline bool shouldAutoDetectRack() { return config->autoDetectRack == DirConfig::ENABLED || config->autoDetectRack == DirConfig::UNSET; } inline bool shouldAutoDetectWSGI() { return config->autoDetectWSGI == DirConfig::ENABLED || config->autoDetectWSGI == DirConfig::UNSET; } public: /** * Create a new DirectoryMapper object. * * @param cstat A CachedFileStat object used for statting files. * @param throttleRate A throttling rate for cstat. * @warning Do not use this object after the destruction of r, * config or cstat. */ DirectoryMapper(request_rec *r, DirConfig *config, CachedFileStat *cstat, unsigned int throttleRate) { this->r = r; this->config = config; this->cstat = cstat; this->throttleRate = throttleRate; appType = NONE; baseURIKnown = false; baseURI = NULL; } /** * Determine whether the given HTTP request falls under one of the specified * RailsBaseURIs or RackBaseURIs. If yes, then the first matching base URI will * be returned. * * If Rails/Rack autodetection was enabled in the configuration, and the document * root seems to be a valid Rails/Rack 'public' folder, then this method will * return "/". * * Otherwise, NULL will be returned. * * @throws FileSystemException This method might also examine the filesystem in * order to detect the application's type. During that process, a * FileSystemException might be thrown. * @warning The return value may only be used as long as config * hasn't been destroyed. */ const char *getBaseURI() { TRACE_POINT(); if (baseURIKnown) { return baseURI; } set::const_iterator it; const char *uri = r->uri; size_t uri_len = strlen(uri); if (uri_len == 0 || uri[0] != '/') { baseURIKnown = true; return NULL; } UPDATE_TRACE_POINT(); for (it = config->railsBaseURIs.begin(); it != config->railsBaseURIs.end(); it++) { const string &base(*it); if ( base == "/" || ( uri_len == base.size() && memcmp(uri, base.c_str(), uri_len) == 0 ) || ( uri_len > base.size() && memcmp(uri, base.c_str(), base.size()) == 0 && uri[base.size()] == '/' ) ) { baseURIKnown = true; baseURI = base.c_str(); appType = RAILS; return baseURI; } } UPDATE_TRACE_POINT(); for (it = config->rackBaseURIs.begin(); it != config->rackBaseURIs.end(); it++) { const string &base(*it); if ( base == "/" || ( uri_len == base.size() && memcmp(uri, base.c_str(), uri_len) == 0 ) || ( uri_len > base.size() && memcmp(uri, base.c_str(), base.size()) == 0 && uri[base.size()] == '/' ) ) { baseURIKnown = true; baseURI = base.c_str(); appType = RACK; return baseURI; } } UPDATE_TRACE_POINT(); if (shouldAutoDetectRack() && verifyRackDir(config->getAppRoot(ap_document_root(r)), cstat, throttleRate)) { baseURIKnown = true; baseURI = "/"; appType = RACK; return baseURI; } UPDATE_TRACE_POINT(); if (shouldAutoDetectRails() && verifyRailsDir(config->getAppRoot(ap_document_root(r)), cstat, throttleRate)) { baseURIKnown = true; baseURI = "/"; appType = RAILS; return baseURI; } UPDATE_TRACE_POINT(); if (shouldAutoDetectWSGI() && verifyWSGIDir(config->getAppRoot(ap_document_root(r)), cstat, throttleRate)) { baseURIKnown = true; baseURI = "/"; appType = WSGI; return baseURI; } baseURIKnown = true; return NULL; } /** * Returns the filename of the 'public' directory of the Rails/Rack application * that's associated with the HTTP request. * * Returns an empty string if the document root of the HTTP request * cannot be determined, or if it isn't a valid folder. * * @throws FileSystemException An error occured while examening the filesystem. */ string getPublicDirectory() { if (!baseURIKnown) { getBaseURI(); } if (baseURI == NULL) { return ""; } const char *docRoot = ap_document_root(r); size_t len = strlen(docRoot); if (len > 0) { string path; if (docRoot[len - 1] == '/') { path.assign(docRoot, len - 1); } else { path.assign(docRoot, len); } if (strcmp(baseURI, "/") != 0) { /* Application is deployed in a sub-URI. * This is probably a symlink, so let's resolve it. */ path.append(baseURI); path = resolveSymlink(path); } return path; } else { return ""; } } /** * Returns the application type that's associated with the HTTP request. * * @throws FileSystemException An error occured while examening the filesystem. */ ApplicationType getApplicationType() { if (!baseURIKnown) { getBaseURI(); } return appType; } /** * Returns the application type (as a string) that's associated * with the HTTP request. * * @throws FileSystemException An error occured while examening the filesystem. */ const char *getApplicationTypeString() { if (!baseURIKnown) { getBaseURI(); } switch (appType) { case RAILS: return "rails"; case RACK: return "rack"; case WSGI: return "wsgi"; default: return NULL; }; } /** * Returns the environment under which the application should be spawned. * * @throws FileSystemException An error occured while examening the filesystem. */ const char *getEnvironment() { switch (getApplicationType()) { case RAILS: return config->getRailsEnv(); case RACK: return config->getRackEnv(); default: return "production"; } } }; } // namespace Passenger #endif /* _PASSENGER_DIRECTORY_MAPPER_H_ */