- Main Page
- Modules
- Classes
- Files

# DirectoryMapper.h

    00001 /*
    00002  *  Phusion Passenger - http://www.modrails.com/
    00003  *  Copyright (C) 2008  Phusion
    00004  *
    00005  *  Phusion Passenger is a trademark of Hongli Lai & Ninh Bui.
    00006  *
    00007  *  This program is free software; you can redistribute it and/or modify
    00008  *  it under the terms of the GNU General Public License as published by
    00009  *  the Free Software Foundation; version 2 of the License.
    00010  *
    00011  *  This program is distributed in the hope that it will be useful,
    00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    00014  *  GNU General Public License for more details.
    00015  *
    00016  *  You should have received a copy of the GNU General Public License along
    00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
    00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    00019  */
    00020 #ifndef _PASSENGER_DIRECTORY_MAPPER_H_
    00021 #define _PASSENGER_DIRECTORY_MAPPER_H_
    00022
    00023 #include <string>
    00024 #include <set>
    00025 #include <cstring>
    00026
    00027 #include <oxt/backtrace.hpp>
    00028
    00029 #include "CachedFileStat.h"
    00030 #include "Configuration.h"
    00031 #include "Utils.h"
    00032
    00033 // The Apache/APR headers *must* come after the Boost headers, otherwise
    00034 // compilation will fail on OpenBSD.
    00035 #include <httpd.h>
    00036 #include <http_core.h>
    00037
    00038 namespace Passenger {
    00039
    00040 using namespace std;
    00041 using namespace oxt;
    00042
    00043 /**
    00044  * Utility class for determining URI-to-application directory mappings.
    00045  * Given a URI, it will determine whether that URI belongs to a Phusion
    00046  * Passenger-handled application, what the base URI of that application is,
    00047  * and what the associated 'public' directory is.
    00048  *
    00049  * @note This class is not thread-safe, but is reentrant.
    00050  * @ingroup Core
    00051  */
    00052 class DirectoryMapper {
    00053 public:
    00054         enum ApplicationType {
    00055                 NONE,
    00056                 RAILS,
    00057                 RACK,
    00058                 WSGI
    00059         };
    00060
    00061 private:
    00062         DirConfig *config;
    00063         request_rec *r;
    00064         CachedMultiFileStat *mstat;
    00065         unsigned int throttleRate;
    00066         bool baseURIKnown;
    00067         const char *baseURI;
    00068         ApplicationType appType;
    00069
    00070         inline bool shouldAutoDetectRails() {
    00071                 return config->autoDetectRails == DirConfig::ENABLED ||
    00072                         config->autoDetectRails == DirConfig::UNSET;
    00073         }
    00074
    00075         inline bool shouldAutoDetectRack() {
    00076                 return config->autoDetectRack == DirConfig::ENABLED ||
    00077                         config->autoDetectRack == DirConfig::UNSET;
    00078         }
    00079
    00080         inline bool shouldAutoDetectWSGI() {
    00081                 return config->autoDetectWSGI == DirConfig::ENABLED ||
    00082                         config->autoDetectWSGI == DirConfig::UNSET;
    00083         }
    00084
    00085 public:
    00086         /**
    00087          * Create a new DirectoryMapper object.
    00088          *
    00089          * @param mstat A CachedMultiFileStat object used for statting files.
    00090          * @param throttleRate A throttling rate for mstat.
    00091          * @warning Do not use this object after the destruction of <tt>r</tt>,
    00092          *          <tt>config</tt> or <tt>mstat</tt>.
    00093          */
    00094         DirectoryMapper(request_rec *r, DirConfig *config,
    00095                         CachedMultiFileStat *mstat, unsigned int throttleRate) {
    00096                 this->r = r;
    00097                 this->config = config;
    00098                 this->mstat = mstat;
    00099                 this->throttleRate = throttleRate;
    00100                 appType = NONE;
    00101                 baseURIKnown = false;
    00102                 baseURI = NULL;
    00103         }
    00104
    00105         /**
    00106          * Determine whether the given HTTP request falls under one of the specified
    00107          * RailsBaseURIs or RackBaseURIs. If yes, then the first matching base URI will
    00108          * be returned.
    00109          *
    00110          * If Rails/Rack autodetection was enabled in the configuration, and the document
    00111          * root seems to be a valid Rails/Rack 'public' folder, then this method will
    00112          * return "/".
    00113          *
    00114          * Otherwise, NULL will be returned.
    00115          *
    00116          * @throws FileSystemException This method might also examine the filesystem in
    00117          *            order to detect the application's type. During that process, a
    00118          *            FileSystemException might be thrown.
    00119          * @warning The return value may only be used as long as <tt>config</tt>
    00120          *          hasn't been destroyed.
    00121          */
    00122         const char *getBaseURI() {
    00123                 TRACE_POINT();
    00124                 if (baseURIKnown) {
    00125                         return baseURI;
    00126                 }
    00127
    00128                 set<string>::const_iterator it;
    00129                 const char *uri = r->uri;
    00130                 size_t uri_len = strlen(uri);
    00131
    00132                 if (uri_len == 0 || uri[0] != '/') {
    00133                         baseURIKnown = true;
    00134                         return NULL;
    00135                 }
    00136
    00137                 UPDATE_TRACE_POINT();
    00138                 for (it = config->railsBaseURIs.begin(); it != config->railsBaseURIs.end(); it++) {
    00139                         const string &base(*it);
    00140                         if (  base == "/"
    00141                          || ( uri_len == base.size() && memcmp(uri, base.c_str(), uri_len) == 0 )
    00142                          || ( uri_len  > base.size() && memcmp(uri, base.c_str(), base.size()) == 0
    00143                                                      && uri[base.size()] == '/' )
    00144                         ) {
    00145                                 baseURIKnown = true;
    00146                                 baseURI = base.c_str();
    00147                                 appType = RAILS;
    00148                                 return baseURI;
    00149                         }
    00150                 }
    00151
    00152                 UPDATE_TRACE_POINT();
    00153                 for (it = config->rackBaseURIs.begin(); it != config->rackBaseURIs.end(); it++) {
    00154                         const string &base(*it);
    00155                         if (  base == "/"
    00156                          || ( uri_len == base.size() && memcmp(uri, base.c_str(), uri_len) == 0 )
    00157                          || ( uri_len  > base.size() && memcmp(uri, base.c_str(), base.size()) == 0
    00158                                                      && uri[base.size()] == '/' )
    00159                         ) {
    00160                                 baseURIKnown = true;
    00161                                 baseURI = base.c_str();
    00162                                 appType = RACK;
    00163                                 return baseURI;
    00164                         }
    00165                 }
    00166
    00167                 UPDATE_TRACE_POINT();
    00168                 if (shouldAutoDetectRails()
    00169                  && verifyRailsDir(config->getAppRoot(ap_document_root(r)), mstat, throttleRate)) {
    00170                         baseURIKnown = true;
    00171                         baseURI = "/";
    00172                         appType = RAILS;
    00173                         return baseURI;
    00174                 }
    00175
    00176                 UPDATE_TRACE_POINT();
    00177                 if (shouldAutoDetectRack()
    00178                  && verifyRackDir(config->getAppRoot(ap_document_root(r)), mstat, throttleRate)) {
    00179                         baseURIKnown = true;
    00180                         baseURI = "/";
    00181                         appType = RACK;
    00182                         return baseURI;
    00183                 }
    00184
    00185                 UPDATE_TRACE_POINT();
    00186                 if (shouldAutoDetectWSGI()
    00187                  && verifyWSGIDir(config->getAppRoot(ap_document_root(r)), mstat, throttleRate)) {
    00188                         baseURIKnown = true;
    00189                         baseURI = "/";
    00190                         appType = WSGI;
    00191                         return baseURI;
    00192                 }
    00193
    00194                 baseURIKnown = true;
    00195                 return NULL;
    00196         }
    00197
    00198         /**
    00199          * Returns the filename of the 'public' directory of the Rails/Rack application
    00200          * that's associated with the HTTP request.
    00201          *
    00202          * Returns an empty string if the document root of the HTTP request
    00203          * cannot be determined, or if it isn't a valid folder.
    00204          *
    00205          * @throws FileSystemException An error occured while examening the filesystem.
    00206          */
    00207         string getPublicDirectory() {
    00208                 if (!baseURIKnown) {
    00209                         getBaseURI();
    00210                 }
    00211                 if (baseURI == NULL) {
    00212                         return "";
    00213                 }
    00214
    00215                 const char *docRoot = ap_document_root(r);
    00216                 size_t len = strlen(docRoot);
    00217                 if (len > 0) {
    00218                         string path;
    00219                         if (docRoot[len - 1] == '/') {
    00220                                 path.assign(docRoot, len - 1);
    00221                         } else {
    00222                                 path.assign(docRoot, len);
    00223                         }
    00224                         if (strcmp(baseURI, "/") != 0) {
    00225                                 path.append(baseURI);
    00226                         }
    00227                         return path;
    00228                 } else {
    00229                         return "";
    00230                 }
    00231         }
    00232
    00233         /**
    00234          * Returns the application type that's associated with the HTTP request.
    00235          *
    00236          * @throws FileSystemException An error occured while examening the filesystem.
    00237          */
    00238         ApplicationType getApplicationType() {
    00239                 if (!baseURIKnown) {
    00240                         getBaseURI();
    00241                 }
    00242                 return appType;
    00243         }
    00244
    00245         /**
    00246          * Returns the application type (as a string) that's associated
    00247          * with the HTTP request.
    00248          *
    00249          * @throws FileSystemException An error occured while examening the filesystem.
    00250          */
    00251         const char *getApplicationTypeString() {
    00252                 if (!baseURIKnown) {
    00253                         getBaseURI();
    00254                 }
    00255                 switch (appType) {
    00256                 case RAILS:
    00257                         return "rails";
    00258                 case RACK:
    00259                         return "rack";
    00260                 case WSGI:
    00261                         return "wsgi";
    00262                 default:
    00263                         return NULL;
    00264                 };
    00265         }
    00266
    00267         /**
    00268          * Returns the environment under which the application should be spawned.
    00269          *
    00270          * @throws FileSystemException An error occured while examening the filesystem.
    00271          */
    00272         const char *getEnvironment() {
    00273                 switch (getApplicationType()) {
    00274                 case RAILS:
    00275                         return config->getRailsEnv();
    00276                 case RACK:
    00277                         return config->getRackEnv();
    00278                 default:
    00279                         return "production";
    00280                 }
    00281         }
    00282 };
    00283
    00284 } // namespace Passenger
    00285
    00286 #endif /* _PASSENGER_DIRECTORY_MAPPER_H_ */
    00287