Passenger
|
00001 /* 00002 * Phusion Passenger - http://www.modrails.com/ 00003 * Copyright (c) 2008, 2009 Phusion 00004 * 00005 * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. 00006 * 00007 * Permission is hereby granted, free of charge, to any person obtaining a copy 00008 * of this software and associated documentation files (the "Software"), to deal 00009 * in the Software without restriction, including without limitation the rights 00010 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 00011 * copies of the Software, and to permit persons to whom the Software is 00012 * furnished to do so, subject to the following conditions: 00013 * 00014 * The above copyright notice and this permission notice shall be included in 00015 * all copies or substantial portions of the Software. 00016 * 00017 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 00018 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 00019 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 00020 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 00021 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 00022 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 00023 * THE SOFTWARE. 00024 */ 00025 #ifndef _PASSENGER_DIRECTORY_MAPPER_H_ 00026 #define _PASSENGER_DIRECTORY_MAPPER_H_ 00027 00028 #include <string> 00029 #include <set> 00030 #include <cstring> 00031 00032 #include <oxt/backtrace.hpp> 00033 00034 #include "CachedFileStat.hpp" 00035 #include "Configuration.h" 00036 #include "Utils.h" 00037 00038 // The Apache/APR headers *must* come after the Boost headers, otherwise 00039 // compilation will fail on OpenBSD. 00040 #include <httpd.h> 00041 #include <http_core.h> 00042 00043 namespace Passenger { 00044 00045 using namespace std; 00046 using namespace oxt; 00047 00048 /** 00049 * Utility class for determining URI-to-application directory mappings. 00050 * Given a URI, it will determine whether that URI belongs to a Phusion 00051 * Passenger-handled application, what the base URI of that application is, 00052 * and what the associated 'public' directory is. 00053 * 00054 * @note This class is not thread-safe, but is reentrant. 00055 * @ingroup Core 00056 */ 00057 class DirectoryMapper { 00058 public: 00059 enum ApplicationType { 00060 NONE, 00061 RAILS, 00062 RACK, 00063 WSGI 00064 }; 00065 00066 private: 00067 DirConfig *config; 00068 request_rec *r; 00069 CachedFileStat *cstat; 00070 unsigned int throttleRate; 00071 bool baseURIKnown; 00072 const char *baseURI; 00073 ApplicationType appType; 00074 00075 inline bool shouldAutoDetectRails() { 00076 return config->autoDetectRails == DirConfig::ENABLED || 00077 config->autoDetectRails == DirConfig::UNSET; 00078 } 00079 00080 inline bool shouldAutoDetectRack() { 00081 return config->autoDetectRack == DirConfig::ENABLED || 00082 config->autoDetectRack == DirConfig::UNSET; 00083 } 00084 00085 inline bool shouldAutoDetectWSGI() { 00086 return config->autoDetectWSGI == DirConfig::ENABLED || 00087 config->autoDetectWSGI == DirConfig::UNSET; 00088 } 00089 00090 public: 00091 /** 00092 * Create a new DirectoryMapper object. 00093 * 00094 * @param cstat A CachedFileStat object used for statting files. 00095 * @param throttleRate A throttling rate for cstat. 00096 * @warning Do not use this object after the destruction of <tt>r</tt>, 00097 * <tt>config</tt> or <tt>cstat</tt>. 00098 */ 00099 DirectoryMapper(request_rec *r, DirConfig *config, 00100 CachedFileStat *cstat, unsigned int throttleRate) { 00101 this->r = r; 00102 this->config = config; 00103 this->cstat = cstat; 00104 this->throttleRate = throttleRate; 00105 appType = NONE; 00106 baseURIKnown = false; 00107 baseURI = NULL; 00108 } 00109 00110 /** 00111 * Determine whether the given HTTP request falls under one of the specified 00112 * RailsBaseURIs or RackBaseURIs. If yes, then the first matching base URI will 00113 * be returned. 00114 * 00115 * If Rails/Rack autodetection was enabled in the configuration, and the document 00116 * root seems to be a valid Rails/Rack 'public' folder, then this method will 00117 * return "/". 00118 * 00119 * Otherwise, NULL will be returned. 00120 * 00121 * @throws FileSystemException This method might also examine the filesystem in 00122 * order to detect the application's type. During that process, a 00123 * FileSystemException might be thrown. 00124 * @warning The return value may only be used as long as <tt>config</tt> 00125 * hasn't been destroyed. 00126 */ 00127 const char *getBaseURI() { 00128 TRACE_POINT(); 00129 if (baseURIKnown) { 00130 return baseURI; 00131 } 00132 00133 set<string>::const_iterator it; 00134 const char *uri = r->uri; 00135 size_t uri_len = strlen(uri); 00136 00137 if (uri_len == 0 || uri[0] != '/') { 00138 baseURIKnown = true; 00139 return NULL; 00140 } 00141 00142 UPDATE_TRACE_POINT(); 00143 for (it = config->railsBaseURIs.begin(); it != config->railsBaseURIs.end(); it++) { 00144 const string &base(*it); 00145 if ( base == "/" 00146 || ( uri_len == base.size() && memcmp(uri, base.c_str(), uri_len) == 0 ) 00147 || ( uri_len > base.size() && memcmp(uri, base.c_str(), base.size()) == 0 00148 && uri[base.size()] == '/' ) 00149 ) { 00150 baseURIKnown = true; 00151 baseURI = base.c_str(); 00152 appType = RAILS; 00153 return baseURI; 00154 } 00155 } 00156 00157 UPDATE_TRACE_POINT(); 00158 for (it = config->rackBaseURIs.begin(); it != config->rackBaseURIs.end(); it++) { 00159 const string &base(*it); 00160 if ( base == "/" 00161 || ( uri_len == base.size() && memcmp(uri, base.c_str(), uri_len) == 0 ) 00162 || ( uri_len > base.size() && memcmp(uri, base.c_str(), base.size()) == 0 00163 && uri[base.size()] == '/' ) 00164 ) { 00165 baseURIKnown = true; 00166 baseURI = base.c_str(); 00167 appType = RACK; 00168 return baseURI; 00169 } 00170 } 00171 00172 UPDATE_TRACE_POINT(); 00173 if (shouldAutoDetectRack() 00174 && verifyRackDir(config->getAppRoot(ap_document_root(r)), cstat, throttleRate)) { 00175 baseURIKnown = true; 00176 baseURI = "/"; 00177 appType = RACK; 00178 return baseURI; 00179 } 00180 00181 UPDATE_TRACE_POINT(); 00182 if (shouldAutoDetectRails() 00183 && verifyRailsDir(config->getAppRoot(ap_document_root(r)), cstat, throttleRate)) { 00184 baseURIKnown = true; 00185 baseURI = "/"; 00186 appType = RAILS; 00187 return baseURI; 00188 } 00189 00190 UPDATE_TRACE_POINT(); 00191 if (shouldAutoDetectWSGI() 00192 && verifyWSGIDir(config->getAppRoot(ap_document_root(r)), cstat, throttleRate)) { 00193 baseURIKnown = true; 00194 baseURI = "/"; 00195 appType = WSGI; 00196 return baseURI; 00197 } 00198 00199 baseURIKnown = true; 00200 return NULL; 00201 } 00202 00203 /** 00204 * Returns the filename of the 'public' directory of the Rails/Rack application 00205 * that's associated with the HTTP request. 00206 * 00207 * Returns an empty string if the document root of the HTTP request 00208 * cannot be determined, or if it isn't a valid folder. 00209 * 00210 * @throws FileSystemException An error occured while examening the filesystem. 00211 */ 00212 string getPublicDirectory() { 00213 if (!baseURIKnown) { 00214 getBaseURI(); 00215 } 00216 if (baseURI == NULL) { 00217 return ""; 00218 } 00219 00220 const char *docRoot = ap_document_root(r); 00221 size_t len = strlen(docRoot); 00222 if (len > 0) { 00223 string path; 00224 if (docRoot[len - 1] == '/') { 00225 path.assign(docRoot, len - 1); 00226 } else { 00227 path.assign(docRoot, len); 00228 } 00229 if (strcmp(baseURI, "/") != 0) { 00230 /* Application is deployed in a sub-URI. 00231 * This is probably a symlink, so let's resolve it. 00232 */ 00233 path.append(baseURI); 00234 path = resolveSymlink(path); 00235 } 00236 return path; 00237 } else { 00238 return ""; 00239 } 00240 } 00241 00242 /** 00243 * Returns the application type that's associated with the HTTP request. 00244 * 00245 * @throws FileSystemException An error occured while examening the filesystem. 00246 */ 00247 ApplicationType getApplicationType() { 00248 if (!baseURIKnown) { 00249 getBaseURI(); 00250 } 00251 return appType; 00252 } 00253 00254 /** 00255 * Returns the application type (as a string) that's associated 00256 * with the HTTP request. 00257 * 00258 * @throws FileSystemException An error occured while examening the filesystem. 00259 */ 00260 const char *getApplicationTypeString() { 00261 if (!baseURIKnown) { 00262 getBaseURI(); 00263 } 00264 switch (appType) { 00265 case RAILS: 00266 return "rails"; 00267 case RACK: 00268 return "rack"; 00269 case WSGI: 00270 return "wsgi"; 00271 default: 00272 return NULL; 00273 }; 00274 } 00275 00276 /** 00277 * Returns the environment under which the application should be spawned. 00278 * 00279 * @throws FileSystemException An error occured while examening the filesystem. 00280 */ 00281 const char *getEnvironment() { 00282 switch (getApplicationType()) { 00283 case RAILS: 00284 return config->getRailsEnv(); 00285 case RACK: 00286 return config->getRackEnv(); 00287 default: 00288 return "production"; 00289 } 00290 } 00291 }; 00292 00293 } // namespace Passenger 00294 00295 #endif /* _PASSENGER_DIRECTORY_MAPPER_H_ */ 00296