X7ROOT File Manager
Current Path:
/opt/cpanel/ea-ruby27/src/passenger-release-6.1.2/src/apache2_module
opt
/
cpanel
/
ea-ruby27
/
src
/
passenger-release-6.1.2
/
src
/
apache2_module
/
??
..
??
Bucket.cpp
(5.43 KB)
??
Bucket.h
(3.54 KB)
??
CBindings.cpp
(1.76 KB)
??
Config.cpp
(7.82 KB)
??
Config.h
(3.04 KB)
??
ConfigGeneral
??
DirConfig
??
DirectoryMapper.h
(8.73 KB)
??
Hooks.cpp
(57.98 KB)
??
Hooks.h
(1.58 KB)
??
README.md
(375 B)
??
ServerConfig
??
Utils.h
(7.04 KB)
??
mod_passenger.c
(2.24 KB)
Editing: Hooks.cpp
/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2025 Asynchronous B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Asynchronous B.V. * * 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. */ /* * This is the main source file which interfaces directly with Apache by * installing hooks. The code here can look a bit convoluted, but it'll make * more sense if you read: * http://httpd.apache.org/docs/2.2/developer/request.html * * Scroll all the way down to passenger_register_hooks to get an idea of * what we're hooking into and what we do in those hooks. There are many * hooks but the gist is implemented in just two methods: prepareRequest() * and handleRequest(). Most hooks exist for implementing compatibility * with other Apache modules. These hooks create an environment in which * prepareRequest() and handleRequest() can be comfortably run. */ // In Apache < 2.4, this macro was necessary for core_dir_config and other structs #define CORE_PRIVATE #include <boost/thread.hpp> #include <sys/time.h> #include <sys/resource.h> #include <sys/stat.h> #include <exception> #include <cstdio> #include <fcntl.h> #include <unistd.h> #include <oxt/initialize.hpp> #include <oxt/macros.hpp> #include <oxt/backtrace.hpp> #include <oxt/detail/context.hpp> #include "Bucket.h" #include "Config.h" #include "DirectoryMapper.h" #include "Utils.h" #include <modp_b64.h> #include <WrapperRegistry/Registry.h> #include <FileTools/FileManip.h> #include <FileTools/FileManip.h> #include <Utils.h> #include <IOTools/IOUtils.h> #include <StrIntTools/StrIntUtils.h> #include <SystemTools/SystemTime.h> #include <Utils/HttpConstants.h> #include <Utils/ReleaseableScopedPointer.h> #include <LoggingKit/LoggingKit.h> #include <LoggingKit/Context.h> #include <WatchdogLauncher.h> #include <Constants.h> /* The Apache/APR headers *must* come after the Boost headers, otherwise * compilation will fail on OpenBSD. */ #include <ap_config.h> #include <ap_release.h> #include <httpd.h> #include <http_config.h> #include <http_core.h> #include <http_request.h> #include <http_protocol.h> #include <http_log.h> #include <util_script.h> #include <apr_pools.h> #include <apr_strings.h> #include <apr_lib.h> #include <unixd.h> #include "DirConfig/AutoGeneratedHeaderSerialization.cpp" extern "C" module AP_MODULE_DECLARE_DATA passenger_module; #ifdef APLOG_USE_MODULE APLOG_USE_MODULE(passenger); #endif #if HTTP_VERSION(AP_SERVER_MAJORVERSION_NUMBER, AP_SERVER_MINORVERSION_NUMBER) > 2002 // Apache > 2.2.x #define AP_GET_SERVER_VERSION_DEPRECATED #elif HTTP_VERSION(AP_SERVER_MAJORVERSION_NUMBER, AP_SERVER_MINORVERSION_NUMBER) == 2002 // Apache == 2.2.x #if AP_SERVER_PATCHLEVEL_NUMBER >= 14 #define AP_GET_SERVER_VERSION_DEPRECATED #endif #endif namespace Passenger { namespace Apache2Module { using namespace std; class Hooks { private: class ErrorReport { public: virtual ~ErrorReport() { } virtual int report(request_rec *r) = 0; }; class ReportFileSystemError: public ErrorReport { private: FileSystemException e; #ifdef __linux__ bool selinuxIsEnforcing() const { FILE *f = fopen("/sys/fs/selinux/enforce", "r"); if (f != NULL) { char buf; size_t ret = fread(&buf, 1, 1, f); fclose(f); return ret == 1 && buf == '1'; } else { return false; } } #endif public: ReportFileSystemError(const FileSystemException &ex): e(ex) { } int report(request_rec *r) { r->status = 500; ap_set_content_type(r, "text/html; charset=UTF-8"); ap_rputs("<h1>Passenger error #2</h1>\n", r); ap_rputs("<p>An error occurred while trying to access '", r); ap_rputs(ap_escape_html(r->pool, e.filename().c_str()), r); ap_rputs("': ", r); ap_rputs(ap_escape_html(r->pool, e.what()), r); ap_rputs("</p>\n", r); if (e.code() == EACCES || e.code() == EPERM) { ap_rputs("<p>", r); ap_rputs("Apache doesn't have read permissions to that file. ", r); ap_rputs("Please fix the relevant file permissions.", r); ap_rputs("</p>\n", r); #ifdef __linux__ if (selinuxIsEnforcing()) { ap_rputs("<p>", r); ap_rputs("The permission problems may also be caused by SELinux restrictions. ", r); ap_rputs("Please read https://www.phusionpassenger.com/library/admin/apache/troubleshooting/?a=apache-cannot-access-my-app-s-files-because-of-selinux-errors ", r); ap_rputs("to learn how to fix SELinux permission issues. ", r); ap_rputs("</p>", r); } #endif } P_ERROR("A filesystem exception occured.\n" << " Message: " << e.what() << "\n" << " Backtrace:\n" << e.backtrace()); return OK; } }; class ReportDocumentRootDeterminationError: public ErrorReport { private: DocumentRootDeterminationError e; public: ReportDocumentRootDeterminationError(const DocumentRootDeterminationError &ex): e(ex) { } int report(request_rec *r) { r->status = 500; ap_set_content_type(r, "text/html; charset=UTF-8"); ap_rputs("<h1>Passenger error #1</h1>\n", r); ap_rputs("Cannot determine the document root for the current request.", r); P_ERROR("Cannot determine the document root for the current request.\n" << " Backtrace:\n" << e.backtrace()); return OK; } }; struct RequestNote { DirectoryMapper mapper; DirConfig *config; ErrorReport *errorReport; const char *handlerBeforeModRewrite; char *filenameBeforeModRewrite; apr_filetype_e oldFileType; const char *handlerBeforeModAutoIndex; bool enabled; RequestNote(const DirectoryMapper &m, DirConfig *c) : mapper(m), config(c) { errorReport = NULL; handlerBeforeModRewrite = NULL; filenameBeforeModRewrite = NULL; oldFileType = APR_NOFILE; handlerBeforeModAutoIndex = NULL; enabled = true; } ~RequestNote() { delete errorReport; } static apr_status_t cleanup(void *p) { delete (RequestNote *) p; return APR_SUCCESS; } }; enum Threeway { YES, NO, UNKNOWN }; Threeway m_hasModRewrite, m_hasModDir, m_hasModAutoIndex, m_hasModXsendfile; WrapperRegistry::Registry wrapperRegistry; CachedFileStat cstat; WatchdogLauncher watchdogLauncher; boost::mutex cstatMutex; boost::mutex configMutex; static Json::Value strsetToJson(const set<string> &input) { Json::Value result(Json::arrayValue); set<string>::const_iterator it, end = input.end(); for (it = input.begin(); it != end; it++) { result.append(*it); } return result; } static Json::Value nonEmptyString(const char *str) { if (str != NULL && *str != '\0') { return str; } else { return Json::Value(); } } static Json::Value nonEmptyString(const string &str) { if (str.empty()) { return Json::Value(); } else { return str; } } inline DirConfig *getDirConfig(request_rec *r) { return (DirConfig *) ap_get_module_config(r->per_dir_config, &passenger_module); } /** * The existance of a request note means that the handler should be run. */ inline RequestNote *getRequestNote(request_rec *r) { void *pointer = 0; apr_pool_userdata_get(&pointer, "Phusion Passenger", r->pool); if (pointer != NULL) { RequestNote *note = (RequestNote *) pointer; if (OXT_LIKELY(note->enabled)) { return note; } else { return 0; } } else { return 0; } } void disableRequestNote(request_rec *r) { RequestNote *note = getRequestNote(r); if (note != NULL) { note->enabled = false; } } StaticString getCoreAddress() const { return watchdogLauncher.getCoreAddress(); } StaticString getCorePassword() const { return watchdogLauncher.getCorePassword(); } /** * Connect to the Passenger core. If it looks like the core crashed, * wait and retry for a short period of time until the core has been * restarted by the watchdog. */ FileDescriptor connectToCore() { TRACE_POINT(); FileDescriptor conn; try { conn.assign(connectToServer(getCoreAddress(), __FILE__, __LINE__), NULL, 0); } catch (const SystemException &e) { if (e.code() == EPIPE || e.code() == ECONNREFUSED || e.code() == ENOENT) { UPDATE_TRACE_POINT(); bool connected = false; // Maybe the core crashed. First wait 50 ms. usleep(50000); // Then try to reconnect to the core for the // next 5 seconds. time_t deadline = time(NULL) + 5; while (!connected && time(NULL) < deadline) { try { conn.assign(connectToServer(getCoreAddress(), __FILE__, __LINE__), NULL, 0); connected = true; } catch (const SystemException &e) { if (e.code() == EPIPE || e.code() == ECONNREFUSED || e.code() == ENOENT) { // Looks like the core hasn't been // restarted yet. Wait between 20 and 100 ms. usleep(20000 + rand() % 80000); // Don't care about thread-safety of rand() } else { throw; } } } if (!connected) { UPDATE_TRACE_POINT(); throw IOException("Cannot connect to the Passenger core at " + getCoreAddress()); } } else { throw; } } return conn; } bool hasModRewrite() { if (m_hasModRewrite == UNKNOWN) { if (ap_find_linked_module("mod_rewrite.c")) { m_hasModRewrite = YES; } else { m_hasModRewrite = NO; } } return m_hasModRewrite == YES; } bool hasModDir() { if (m_hasModDir == UNKNOWN) { if (ap_find_linked_module("mod_dir.c")) { m_hasModDir = YES; } else { m_hasModDir = NO; } } return m_hasModDir == YES; } bool hasModAutoIndex() { if (m_hasModAutoIndex == UNKNOWN) { if (ap_find_linked_module("mod_autoindex.c")) { m_hasModAutoIndex = YES; } else { m_hasModAutoIndex = NO; } } return m_hasModAutoIndex == YES; } bool hasModXsendfile() { if (m_hasModXsendfile == UNKNOWN) { if (ap_find_linked_module("mod_xsendfile.c")) { m_hasModXsendfile = YES; } else { m_hasModXsendfile = NO; } } return m_hasModXsendfile == YES; } static bool stderrEqualsFile(const char *path) { struct stat s1, s2; if (fstat(STDERR_FILENO, &s1) == -1) { return false; } // No O_CREAT: we don't care if the file does not exist. int fd = open(path, O_WRONLY | O_APPEND, 0600); if (fd == -1) { return false; } if (fstat(fd, &s2) == -1) { close(fd); return false; } close(fd); return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_rdev == s2.st_rdev; } /** * Gather some information about the request and do some preparations. * * This method will determine whether the Phusion Passenger handler method * should be run for this request, through the following checks: * (B) There is a backend application defined for this URI. * (C) r->filename already exists, meaning that this URI already maps to an existing file. * (D) There is a page cache file for this URI. * * - If B is not true, or if C is true, then the handler shouldn't be run. * - If D is true, then we first transform r->filename to the page cache file's * filename, and then we let Apache serve it statically. The Phusion Passenger * handler shouldn't be run. * - If D is not true, then the handler should be run. * * @pre config->getEnabled() * @param coreModuleWillBeRun Whether the core.c map_to_storage hook might be called after this. * @return Whether the Phusion Passenger handler hook method should be run. * When true, this method will save a request note object so that future hooks * can store request-specific information. */ bool prepareRequest(request_rec *r, DirConfig *config, const char *filename, bool coreModuleWillBeRun = false) { TRACE_POINT(); DirectoryMapper mapper(r, config, wrapperRegistry, &cstat, &cstatMutex, serverConfig.statThrottleRate, &configMutex); try { if (config->getAppStartCommand().empty() && mapper.getDetectorResult().isNull()) { // (B) is not true. disableRequestNote(r); return false; } } catch (const DocumentRootDeterminationError &e) { ReleaseableScopedPointer<RequestNote> note(new RequestNote(mapper, config)); note.get()->errorReport = new ReportDocumentRootDeterminationError(e); apr_pool_userdata_set(note.release(), "Phusion Passenger", RequestNote::cleanup, r->pool); return true; } catch (const FileSystemException &e) { /* DirectoryMapper tried to examine the filesystem in order * to autodetect the application type (e.g. by checking whether * environment.rb exists. But something went wrong, probably * because of a permission problem. This usually * means that the user is trying to deploy an application, but * set the wrong permissions on the relevant folders. * Later, in the handler hook, we inform the user about this * problem so that he can either disable Phusion Passenger's * autodetection routines, or fix the permissions. * * If it's not a permission problem then we'll disable * Phusion Passenger for the rest of the request. */ if (e.code() == EACCES || e.code() == EPERM) { ReleaseableScopedPointer<RequestNote> note(new RequestNote(mapper, config)); note.get()->errorReport = new ReportFileSystemError(e); apr_pool_userdata_set(note.release(), "Phusion Passenger", RequestNote::cleanup, r->pool); return true; } else { disableRequestNote(r); return false; } } // (B) is true. try { FileType fileType = getFileType(filename); if (fileType == FT_REGULAR) { // (C) is true. disableRequestNote(r); return false; } // (C) is not true. Check whether (D) is true. char *pageCacheFile; /* Only GET requests may hit the page cache. This is * important because of REST conventions, e.g. * 'POST /foo' maps to 'FooController#create', * while 'GET /foo' maps to 'FooController#index'. * We wouldn't want our page caching support to interfere * with that. */ if (r->method_number == M_GET) { if (fileType == FT_DIRECTORY) { size_t len; len = strlen(filename); if (len > 0 && filename[len - 1] == '/') { pageCacheFile = apr_pstrcat(r->pool, filename, "index.html", (char *) NULL); } else { pageCacheFile = apr_pstrcat(r->pool, filename, ".html", (char *) NULL); } } else { pageCacheFile = apr_pstrcat(r->pool, filename, ".html", (char *) NULL); } if (!fileExists(pageCacheFile)) { pageCacheFile = NULL; } } else { pageCacheFile = NULL; } if (pageCacheFile != NULL) { // (D) is true. r->filename = pageCacheFile; r->canonical_filename = pageCacheFile; if (!coreModuleWillBeRun) { r->finfo.filetype = APR_NOFILE; ap_set_content_type(r, "text/html"); ap_directory_walk(r); ap_file_walk(r); } return false; } else { // (D) is not true. RequestNote *note = new RequestNote(mapper, config); apr_pool_userdata_set(note, "Phusion Passenger", RequestNote::cleanup, r->pool); return true; } } catch (const FileSystemException &e) { /* Something went wrong while accessing the directory in which * r->filename lives. We already know that this URI belongs to * a backend application, so this error probably means that the * user set the wrong permissions for his 'public' folder. We * don't let the handler hook run so that Apache can decide how * to display the error. */ disableRequestNote(r); return false; } } /** * Most of the high-level logic for forwarding a request to the * Passenger core is contained in this method. */ int handleRequest(request_rec *r) { /********** Step 1: preparation work **********/ /* Initialize OXT backtrace support if not already done for this thread */ if (oxt::get_thread_local_context() == NULL) { /* There is no need to cleanup the context. Apache uses a static * number of threads per process. */ thread_local_context_ptr context = thread_local_context::make_shared_ptr(); unsigned long tid = (unsigned long) pthread_self(); context->thread_name = "Worker " + integerToHex(tid); oxt::set_thread_local_context(context); } /* Check whether an error occured in prepareRequest() that should be reported * to the browser. */ RequestNote *note = getRequestNote(r); if (note == NULL) { return DECLINED; } else if (note->errorReport != NULL) { /* Did an error occur in any of the previous hook methods during * this request? If so, show the error and stop here. */ return note->errorReport->report(r); } else if (r->handler != NULL && strcmp(r->handler, "redirect-handler") == 0) { // mod_rewrite is at work. return DECLINED; } /* mod_mime might have set the httpd/unix-directory Content-Type * if it detects that the current URL maps to a directory. We do * not want to preserve that Content-Type. */ ap_set_content_type(r, NULL); TRACE_POINT(); DirConfig *config = note->config; DirectoryMapper &mapper = note->mapper; try { mapper.getPublicDirectory(); } catch (const DocumentRootDeterminationError &e) { return ReportDocumentRootDeterminationError(e).report(r); } catch (const FileSystemException &e) { /* The application root cannot be determined. This could * happen if, for example, the user specified 'RailsBaseURI /foo' * while there is no filesystem entry called "foo" in the virtual * host's document root. */ return ReportFileSystemError(e).report(r); } UPDATE_TRACE_POINT(); try { /********** Step 2: handle HTTP upload data, if any **********/ int httpStatus = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); if (httpStatus != OK) { return httpStatus; } boost::this_thread::disable_interruption di; boost::this_thread::disable_syscall_interruption dsi; bool expectingBody; expectingBody = ap_should_client_block(r); /********** Step 3: forwarding the request and request body to the Passenger core **********/ int ret; bool bodyIsChunked = false; string headers = constructRequestHeaders(r, mapper, bodyIsChunked); FileDescriptor conn = connectToCore(); writeExact(conn, headers); headers.clear(); if (expectingBody) { sendRequestBody(conn, r, bodyIsChunked); } /********** Step 4: forwarding the response from the Passenger core back to the HTTP client **********/ UPDATE_TRACE_POINT(); apr_bucket_brigade *bb; apr_bucket *b; PassengerBucketStatePtr bucketState; /* Setup the bucket brigade. */ bb = apr_brigade_create(r->connection->pool, r->connection->bucket_alloc); bucketState = boost::make_shared<PassengerBucketState>(conn); b = passenger_bucket_create(bucketState, r->connection->bucket_alloc, config->getBufferResponse()); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnull-pointer-subtraction" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-pointer-subtraction" APR_BRIGADE_INSERT_TAIL(bb, b); #pragma GCC diagnostic pop #pragma clang diagnostic pop b = apr_bucket_eos_create(r->connection->bucket_alloc); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnull-pointer-subtraction" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-pointer-subtraction" APR_BRIGADE_INSERT_TAIL(bb, b); #pragma GCC diagnostic pop #pragma clang diagnostic pop /* Now read the HTTP response header, parse it and fill relevant * information in our request_rec structure. We skip the status line * because ap_scan_script_header_err_brigade() can't handle it. */ /* I know the required size for backendData because I read * util_script.c's source. :-( */ char backendData[MAX_STRING_LEN]; getsfunc_BRIGADE(backendData, MAX_STRING_LEN, bb); // The bucket brigade is an interface to the HTTP response sent by the // PassengerAgent. The scanner parses (line by line) response headers // into error_headers_out (mostly) as well as headers_out. ret = ap_scan_script_header_err_brigade(r, bb, backendData); // The PassengerAgent sets the Connection: close header because it wants // the bb connection closed, but because we fed everything to the // ap_scan_script it will also be set in the response to the client and // that breaks HTTP 1.1 keep-alive, so unset it. apr_table_unset(r->err_headers_out, "Connection"); // It's undefined in which of the tables it ends up in, so unset on both. apr_table_unset(r->headers_out, "Connection"); if (ret == OK) { // The API documentation for ap_scan_script_err_brigade() says it // returns HTTP_OK on success, but it actually returns OK. /* We were able to parse the HTTP response header sent by the * backend process! Proceed with passing the bucket brigade, * for forwarding the response body to the HTTP client. */ /* Manually set the Status header because * ap_scan_script_header_err_brigade() filters it * out. Some broken HTTP clients depend on the * Status header for retrieving the HTTP status. */ if (!r->status_line || *r->status_line == '\0') { r->status_line = getStatusCodeAndReasonPhrase(r->status); if (r->status_line == NULL) { r->status_line = apr_psprintf(r->pool, "%d Unknown Status", r->status); } } apr_table_setn(r->headers_out, "Status", r->status_line); UPDATE_TRACE_POINT(); if (config->getErrorOverride() && ap_is_HTTP_ERROR(r->status)) { /* Send ErrorDocument. * Clear r->status for override error, otherwise ErrorDocument * thinks that this is a recursive error, and doesn't find the * custom error page. */ int originalStatus = r->status; r->status = HTTP_OK; return originalStatus; } else if (ap_pass_brigade(r->output_filters, bb) == APR_SUCCESS) { apr_brigade_cleanup(bb); } return OK; } else { // Passenger core sent an empty response, or an invalid response. apr_brigade_cleanup(bb); apr_table_setn(r->err_headers_out, "Status", "500 Internal Server Error"); return HTTP_INTERNAL_SERVER_ERROR; } } catch (const thread_interrupted &e) { P_TRACE(3, "A system call was interrupted during an HTTP request. Apache " "is probably restarting or shutting down. Backtrace:\n" << e.backtrace()); return HTTP_INTERNAL_SERVER_ERROR; } catch (const tracable_exception &e) { P_ERROR("Unexpected error in mod_passenger: " << e.what() << "\n" << " Backtrace:\n" << e.backtrace()); return HTTP_INTERNAL_SERVER_ERROR; } catch (const std::exception &e) { P_ERROR("Unexpected error in mod_passenger: " << e.what() << "\n" << " Backtrace: not available"); return HTTP_INTERNAL_SERVER_ERROR; } } unsigned int escapeUri(unsigned char *dst, const unsigned char *src, size_t size) { static const char hex[] = "0123456789abcdef"; /* " ", "#", "%", "?", %00-%1F, %7F-%FF */ static uint32_t escape[] = { 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ 0x80000029, /* 1000 0000 0000 0000 0000 0000 0010 1001 */ /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ /* ~}| {zyx wvut srqp onml kjih gfed cba` */ 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ 0xffffffff /* 1111 1111 1111 1111 1111 1111 1111 1111 */ }; if (dst == NULL) { /* find the number of the characters to be escaped */ unsigned int n = 0; while (size > 0) { if (escape[*src >> 5] & (1 << (*src & 0x1f))) { n++; } src++; size--; } return n; } while (size > 0) { if (escape[*src >> 5] & (1 << (*src & 0x1f))) { *dst++ = '%'; *dst++ = hex[*src >> 4]; *dst++ = hex[*src & 0xf]; src++; } else { *dst++ = *src++; } size--; } return 0; } /** * Convert an HTTP header name to a CGI environment name. */ char *httpToEnv(apr_pool_t *p, const char *headerName, size_t len) { char *result = apr_pstrcat(p, "HTTP_", headerName, (char *) NULL); char *current = result + sizeof("HTTP_") - 1; while (*current != '\0') { if (*current == '-') { *current = '_'; } else { *current = apr_toupper(*current); } current++; } return result; } const char *lookupInTable(apr_table_t *table, const char *name) { const apr_array_header_t *headers = apr_table_elts(table); apr_table_entry_t *elements = (apr_table_entry_t *) headers->elts; for (int i = 0; i < headers->nelts; i++) { if (elements[i].key != NULL && strcasecmp(elements[i].key, name) == 0) { return elements[i].val; } } return NULL; } const char *lookupEnv(request_rec *r, const char *name) { return lookupInTable(r->subprocess_env, name); } bool connectionUpgradeFlagSet(const char *header) const { size_t headerSize = strlen(header); if (headerSize < 1024) { char buffer[headerSize + 1]; return connectionUpgradeFlagSet(header, headerSize, buffer, headerSize + 1); } else { DynamicBuffer buffer(headerSize + 1); return connectionUpgradeFlagSet(header, headerSize, buffer.data, headerSize + 1); } } bool connectionUpgradeFlagSet(const char *header, size_t headerSize, char *buffer, size_t bufsize) const { assert(bufsize > headerSize); convertLowerCase((const unsigned char *) header, (unsigned char *) buffer, headerSize); buffer[headerSize] = '\0'; return strstr(buffer, "upgrade"); } string constructRequestHeaders(request_rec *r, DirectoryMapper &mapper, bool &bodyIsChunked) { const char *baseURI = mapper.getBaseURI(); DirConfig *config = getDirConfig(r); string result; // Construct HTTP status line. result.reserve(4096); result.append(r->method); result.append(" ", 1); if (config->getAllowEncodedSlashes()) { /* * Apache decodes encoded slashes in r->uri, so we must use r->unparsed_uri * if we are to support encoded slashes. However mod_rewrite doesn't change * r->unparsed_uri, so the user must make a choice between mod_rewrite * support or encoded slashes support. Sucks. :-( * * http://code.google.com/p/phusion-passenger/issues/detail?id=113 * http://code.google.com/p/phusion-passenger/issues/detail?id=230 */ result.append(r->unparsed_uri); } else { size_t uriLen = strlen(r->uri); unsigned int escaped = escapeUri(NULL, (const unsigned char *) r->uri, uriLen); size_t escapedUriLen = uriLen + 2 * escaped; char *escapedUri = (char *) apr_palloc(r->pool, escapedUriLen); escapeUri((unsigned char *) escapedUri, (const unsigned char *) r->uri, uriLen); result.append(escapedUri, escapedUriLen); if (r->args != NULL) { result.append("?", 1); result.append(r->args); } } result.append(" HTTP/1.1\r\n", sizeof(" HTTP/1.1\r\n") - 1); // Construct HTTP headers. const apr_array_header_t *hdrs_arr; apr_table_entry_t *hdrs; apr_table_entry_t *connectionHeader = NULL; apr_table_entry_t *transferEncodingHeader = NULL; int i; hdrs_arr = apr_table_elts(r->headers_in); hdrs = (apr_table_entry_t *) hdrs_arr->elts; for (i = 0; i < hdrs_arr->nelts; ++i) { if (hdrs[i].key == NULL) { continue; } else if (connectionHeader == NULL && strcasecmp(hdrs[i].key, "Connection") == 0) { connectionHeader = &hdrs[i]; } else if (transferEncodingHeader == NULL && strcasecmp(hdrs[i].key, "Transfer-Encoding") == 0) { transferEncodingHeader = &hdrs[i]; } else { result.append(hdrs[i].key); result.append(": ", 2); if (hdrs[i].val != NULL) { result.append(hdrs[i].val); } result.append("\r\n", 2); } } if (connectionHeader != NULL && connectionUpgradeFlagSet(connectionHeader->val)) { result.append("Connection: upgrade\r\n", sizeof("Connection: upgrade\r\n") - 1); } else { result.append("Connection: close\r\n", sizeof("Connection: close\r\n") - 1); } if (transferEncodingHeader != NULL) { result.append("Transfer-Encoding: ", sizeof("Transfer-Encoding: ") - 1); result.append(transferEncodingHeader->val); result.append("\r\n", 2); bodyIsChunked = strcasecmp(transferEncodingHeader->val, "chunked") == 0; } // Add secure headers. result.append("!~: ", sizeof("!~: ") - 1); result.append(getCorePassword().data(), getCorePassword().size()); result.append("\r\n!~DOCUMENT_ROOT: ", sizeof("\r\n!~DOCUMENT_ROOT: ") - 1); result.append(ap_document_root(r)); result.append("\r\n", 2); if (baseURI != NULL) { result.append("!~SCRIPT_NAME: ", sizeof("!~SCRIPT_NAME: ") - 1); result.append(baseURI); result.append("\r\n", 2); } #if HTTP_VERSION(AP_SERVER_MAJORVERSION_NUMBER, AP_SERVER_MINORVERSION_NUMBER) >= 2004 addHeader(result, P_STATIC_STRING("!~REMOTE_ADDR"), r->useragent_ip); addHeader(r, result, P_STATIC_STRING("!~REMOTE_PORT"), r->connection->client_addr->port); #else addHeader(result, P_STATIC_STRING("!~REMOTE_ADDR"), r->connection->remote_ip); addHeader(r, result, P_STATIC_STRING("!~REMOTE_PORT"), r->connection->remote_addr->port); #endif addHeader(result, P_STATIC_STRING("!~REMOTE_USER"), r->user); // App group name. if (config->getAppGroupName().empty()) { result.append("!~PASSENGER_APP_GROUP_NAME: ", sizeof("!~PASSENGER_APP_GROUP_NAME: ") - 1); result.append(mapper.getAppRoot()); if (!config->getAppEnv().empty()) { result.append(" (", 2); result.append(config->getAppEnv().data(), config->getAppEnv().size()); result.append(")", 1); } result.append("\r\n", 2); } // Phusion Passenger options. addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_ROOT"), mapper.getAppRoot()); if (!config->getAppStartCommand().empty()) { addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_START_COMMAND"), config->getAppStartCommand()); } else if (mapper.getDetectorResult().wrapperRegistryEntry != NULL) { addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_TYPE"), mapper.getDetectorResult().wrapperRegistryEntry->language); } else { addHeader(result, P_STATIC_STRING("!~PASSENGER_APP_START_COMMAND"), mapper.getDetectorResult().appStartCommand); } constructRequestHeaders_autoGenerated(r, config, result); /*********************/ /*********************/ // Add environment variables. const apr_array_header_t *env_arr; env_arr = apr_table_elts(r->subprocess_env); if (env_arr->nelts > 0) { apr_table_entry_t *env; string envvarsData; char *envvarsBase64Data; size_t envvarsBase64Len; env = (apr_table_entry_t*) env_arr->elts; for (i = 0; i < env_arr->nelts; ++i) { if ((strcmp(env[i].key, "SCRIPT_NAME") == 0) || (strcmp(env[i].key, "PATH_INFO") == 0)) { continue; } envvarsData.append(env[i].key); envvarsData.append("\0", 1); if (env[i].val != NULL) { envvarsData.append(env[i].val); } envvarsData.append("\0", 1); } envvarsBase64Data = (char *) malloc(modp_b64_encode_len( envvarsData.size())); if (envvarsBase64Data == NULL) { throw RuntimeException("Unable to allocate memory for base64 " "encoding of environment variables"); } envvarsBase64Len = modp_b64_encode(envvarsBase64Data, envvarsData.data(), envvarsData.size()); if (envvarsBase64Len == (size_t) -1) { free(envvarsBase64Data); throw RuntimeException("Unable to base64 encode environment variables"); } result.append("!~PASSENGER_ENV_VARS: ", sizeof("!~PASSENGER_ENV_VARS: ") - 1); result.append(envvarsBase64Data, envvarsBase64Len); result.append("\r\n", 2); free(envvarsBase64Data); } // Add flags. // C = Strip 100 Continue header // D = Dechunk // B = Buffer request body // S = SSL result.append("!~FLAGS: CD", sizeof("!~FLAGS: CD") - 1); if (config->getBufferUpload()) { result.append("B", 1); } if (lookupEnv(r, "HTTPS") != NULL) { result.append("S", 1); } result.append("\r\n\r\n", 4); return result; } static int getsfunc_BRIGADE(char *buf, int len, void *arg) { apr_bucket_brigade *bb = (apr_bucket_brigade *)arg; const char *dst_end = buf + len - 1; /* leave room for terminating null */ char *dst = buf; apr_bucket *e = APR_BRIGADE_FIRST(bb); apr_status_t rv; int done = 0; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnull-pointer-subtraction" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-pointer-subtraction" while ((dst < dst_end) && !done && e != APR_BRIGADE_SENTINEL(bb) && !APR_BUCKET_IS_EOS(e)) { #pragma GCC diagnostic pop #pragma clang diagnostic pop const char *bucket_data; apr_size_t bucket_data_len; const char *src; const char *src_end; apr_bucket * next; rv = apr_bucket_read(e, &bucket_data, &bucket_data_len, APR_BLOCK_READ); if (rv != APR_SUCCESS || (bucket_data_len == 0)) { *dst = '\0'; return APR_STATUS_IS_TIMEUP(rv) ? -1 : 0; } src = bucket_data; src_end = bucket_data + bucket_data_len; while ((src < src_end) && (dst < dst_end) && !done) { if (*src == '\n') { done = 1; } else if (*src != '\r') { *dst++ = *src; } src++; } if (src < src_end) { apr_bucket_split(e, src - bucket_data); } next = APR_BUCKET_NEXT(e); APR_BUCKET_REMOVE(e); apr_bucket_destroy(e); e = next; } *dst = 0; return done; } /** * Reads the next chunk of the request body and put it into a buffer. * * This is like ap_get_client_block(), but can actually report errors * in a sane way. ap_get_client_block() tells you that something went * wrong, but not *what* went wrong. * * @param r The current request. * @param buffer A buffer to put the read data into. * @param bufsiz The size of the buffer. * @return The number of bytes read, or 0 on EOF. * @throws RuntimeException Something non-I/O related went wrong, e.g. * failure to allocate memory and stuff. * @throws IOException An I/O error occurred while trying to read the * request body data. */ unsigned long readRequestBodyFromApache(request_rec *r, char *buffer, apr_size_t bufsiz) { apr_status_t rv; apr_bucket_brigade *bb; if (r->remaining < 0 || (!r->read_chunked && r->remaining == 0)) { return 0; } bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); if (bb == NULL) { r->connection->keepalive = AP_CONN_CLOSE; throw RuntimeException("An error occurred while receiving HTTP upload data: " "unable to create a bucket brigade. Maybe the system doesn't have " "enough free memory."); } rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES, APR_BLOCK_READ, bufsiz); /* We lose the failure code here. This is why ap_get_client_block should * not be used. */ if (rv != APR_SUCCESS) { /* if we actually fail here, we want to just return and * stop trying to read data from the client. */ r->connection->keepalive = AP_CONN_CLOSE; apr_brigade_destroy(bb); char buf[150], *errorString, message[1024]; errorString = apr_strerror(rv, buf, sizeof(buf)); if (errorString != NULL) { snprintf(message, sizeof(message), "An error occurred while receiving HTTP upload data: %s (%d)", errorString, rv); } else { snprintf(message, sizeof(message), "An error occurred while receiving HTTP upload data: unknown error %d", rv); } message[sizeof(message) - 1] = '\0'; throw RuntimeException(message); } /* If this fails, it means that a filter is written incorrectly and that * it needs to learn how to properly handle APR_BLOCK_READ requests by * returning data when requested. */ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnull-pointer-subtraction" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-pointer-subtraction" // we have no control over how this is implemented if (APR_BRIGADE_EMPTY(bb)) { #pragma GCC diagnostic pop #pragma clang diagnostic pop throw RuntimeException("An error occurred while receiving HTTP upload data: " "the next filter in the input filter chain has " "a bug. Please contact the author who wrote this filter about " "this. This problem is not caused by Phusion Passenger."); } /* Check to see if EOS in the brigade. * * If so, we have to leave a nugget for the *next* readRequestBodyFromApache() * call to return 0. */ if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) { if (r->read_chunked) { r->remaining = -1; } else { r->remaining = 0; } } rv = apr_brigade_flatten(bb, buffer, &bufsiz); if (rv != APR_SUCCESS) { apr_brigade_destroy(bb); char buf[150], *errorString, message[1024]; errorString = apr_strerror(rv, buf, sizeof(buf)); if (errorString != NULL) { snprintf(message, sizeof(message), "An error occurred while receiving HTTP upload data: %s (%d)", errorString, rv); } else { snprintf(message, sizeof(message), "An error occurred while receiving HTTP upload data: unknown error %d", rv); } message[sizeof(message) - 1] = '\0'; throw IOException(message); } /* XXX yank me? */ r->read_length += bufsiz; apr_brigade_destroy(bb); return bufsiz; } void sendRequestBody(const FileDescriptor &fd, request_rec *r, bool chunk) { TRACE_POINT(); char buf[1024 * 32]; apr_off_t len; try { while ((len = readRequestBodyFromApache(r, buf, sizeof(buf))) > 0) { if (chunk) { const apr_off_t BUFSIZE = 2 * sizeof(apr_off_t) + 3; char buf[BUFSIZE]; char *pos; const char *end = buf + BUFSIZE; pos = buf + integerToHex<apr_off_t>(len, buf); pos = appendData(pos, end, P_STATIC_STRING("\r\n")); writeExact(fd, buf, pos - buf); } writeExact(fd, buf, len); if (chunk) { writeExact(fd, "\r\n"); } } if (chunk) { writeExact(fd, "0\r\n\r\n"); } } catch (const SystemException &e) { if (e.code() == EPIPE || e.code() == ECONNRESET) { // The Passenger core stopped reading the body, probably // because the application already sent EOF. return; } else { throw e; } } } public: Hooks(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) : cstat(1024), watchdogLauncher(IM_APACHE) { wrapperRegistry.finalize(); postprocessConfig(s, pconf, ptemp); Json::Value loggingConfig; loggingConfig["level"] = LoggingKit::Level(serverConfig.logLevel); loggingConfig["redirect_stderr"] = false; if (!serverConfig.logFile.empty()) { loggingConfig["target"] = serverConfig.logFile.toString(); } if (!serverConfig.fileDescriptorLogFile.empty()) { loggingConfig["file_descriptor_log_target"] = serverConfig.fileDescriptorLogFile.toString(); } vector<ConfigKit::Error> errors; LoggingKit::ConfigChangeRequest req; bool ok; try { ok = LoggingKit::context->prepareConfigChange(loggingConfig, errors, req); } catch (const std::exception &e) { ok = false; fprintf(stderr, "ERROR: unable to configure logging system: %s\n", e.what()); } if (ok) { LoggingKit::context->commitConfigChange(req); } else { fprintf(stderr, "ERROR: unable to configuring logging system: %s\n", ConfigKit::toString(errors).c_str()); } m_hasModRewrite = UNKNOWN; m_hasModDir = UNKNOWN; m_hasModAutoIndex = UNKNOWN; m_hasModXsendfile = UNKNOWN; P_DEBUG("Initializing Phusion Passenger..."); ap_add_version_component(pconf, SERVER_TOKEN_NAME "/" PASSENGER_VERSION); if (serverConfig.root.empty()) { throw ConfigurationException("The 'PassengerRoot' configuration option " "is not specified. This option is required, so please specify it. " "TIP: The correct value for this option was given to you by " "'passenger-install-apache2-module'."); } #ifdef AP_GET_SERVER_VERSION_DEPRECATED const char *webServerDesc = ap_get_server_description(); #else const char *webServerDesc = ap_get_server_version(); #endif ap_version_t version; ap_get_server_revision(&version); string webServerVersion = toString(version.major) + "." + toString(version.minor) + "." + toString(version.patch); if (version.add_string != NULL) { webServerVersion.append(version.add_string); } // Note: WatchdogLauncher::start() sets a number of default values. Json::Value config; config["web_server_module_version"] = PASSENGER_VERSION; config["web_server_version"] = webServerVersion; config["server_software"] = webServerDesc; config["multi_app"] = true; config["default_load_shell_envvars"] = true; config["default_preload_bundler"] = false; config["config_manifest"] = serverConfig.manifest; config["file_descriptor_log_target"] = nonEmptyString(serverConfig.fileDescriptorLogFile); config["controller_socket_backlog"] = serverConfig.socketBacklog; config["controller_file_buffered_channel_buffer_dir"] = nonEmptyString(serverConfig.dataBufferDir); config["instance_registry_dir"] = nonEmptyString(serverConfig.instanceRegistryDir); config["spawn_dir"] = nonEmptyString(serverConfig.spawnDir); config["security_update_checker_disabled"] = serverConfig.disableSecurityUpdateCheck; config["security_update_checker_proxy_url"] = nonEmptyString(serverConfig.securityUpdateCheckProxy); config["telemetry_collector_disabled"] = serverConfig.disableAnonymousTelemetry; config["telemetry_collector_proxy_url"] = nonEmptyString(serverConfig.anonymousTelemetryProxy); config["user_switching"] = serverConfig.userSwitching; config["default_user"] = serverConfig.defaultUser.toString(); config["default_group"] = serverConfig.defaultGroup.toString(); config["default_ruby"] = serverConfig.defaultRuby.toString(); config["show_version_in_header"] = serverConfig.showVersionInHeader; config["max_pool_size"] = serverConfig.maxPoolSize; config["pool_idle_time"] = serverConfig.poolIdleTime; config["max_instances_per_app"] = serverConfig.maxInstancesPerApp; config["response_buffer_high_watermark"] = serverConfig.responseBufferHighWatermark; config["stat_throttle_rate"] = serverConfig.statThrottleRate; config["turbocaching"] = serverConfig.turbocaching; config["old_routing"] = serverConfig.oldRouting; config["prestart_urls"] = strsetToJson(serverConfig.prestartURLs); config["disable_log_prefix"] = serverConfig.disableLogPrefix; if (!serverConfig.logFile.empty()) { config["log_target"] = serverConfig.logFile.toString(); } else if (s->error_fname == NULL) { throw ConfigurationException("Cannot initialize " PROGRAM_NAME " because Apache is not configured with an error log file." " Please either configure Apache with an error log file" " (with the ErrorLog directive), or configure " PROGRAM_NAME " with a `PassengerLogFile` directive."); } else if (s->error_fname[0] == '|') { throw ConfigurationException("Apache is configured to log to a pipe," " so " SHORT_PROGRAM_NAME " cannot be initialized because it doesn't" " support logging to a pipe. Please configure " SHORT_PROGRAM_NAME " with an explicit log file using the `PassengerLogFile` directive."); } else if (strcmp(s->error_fname, "syslog") == 0) { throw ConfigurationException("Apache is configured to log to syslog," " so " SHORT_PROGRAM_NAME " cannot be initialized because it doesn't" " support logging to syslog. Please configure " SHORT_PROGRAM_NAME " with an explicit log file using the `PassengerLogFile` directive."); } else { config["log_target"]["path"] = ap_server_root_relative(pconf, s->error_fname); if (stderrEqualsFile(ap_server_root_relative(pconf, s->error_fname))) { config["log_target"]["stderr"] = true; } } Json::Value::iterator it, end = serverConfig.ctl.end(); for (it = serverConfig.ctl.begin(); it != end; it++) { config[it.name()] = *it; } watchdogLauncher.start(serverConfig.root, config); } void childInit(apr_pool_t *pchild, server_rec *s) { watchdogLauncher.detach(); } int prepareRequestWhenInHighPerformanceMode(request_rec *r) { DirConfig *config = getDirConfig(r); if (config->getEnabled() && config->getHighPerformance()) { if (prepareRequest(r, config, r->filename, true)) { return OK; } else { return DECLINED; } } else { return DECLINED; } } /** * This is the hook method for the map_to_storage hook. Apache's final map_to_storage hook * method (defined in core.c) will do the following: * * If r->filename doesn't exist, then it will change the filename to the * following form: * * A/B * * A is top-most directory that exists. B is the first filename piece that * normally follows A. For example, suppose that a website's DocumentRoot * is /website, on server http://test.com/. Suppose that there's also a * directory /website/images. No other files or directories exist in /website. * * If we access: then r->filename will be: * http://test.com/foo/bar /website/foo * http://test.com/foo/bar/baz /website/foo * http://test.com/images/foo/bar /website/images/foo * * We obviously don't want this to happen because it'll interfere with our page * cache file search code. So here we save the original value of r->filename so * that we can use it later. */ int saveOriginalFilename(request_rec *r) { apr_table_set(r->notes, "Phusion Passenger: original filename", r->filename); return DECLINED; } int prepareRequestWhenNotInHighPerformanceMode(request_rec *r) { DirConfig *config = getDirConfig(r); if (config->getEnabled()) { if (config->getHighPerformance()) { /* Preparations have already been done in the map_to_storage hook. * Prevent other modules' fixups hooks from being run. */ return OK; } else { /* core.c's map_to_storage hook will transform the filename, as * described by saveOriginalFilename(). Here we restore the * original filename. */ const char *filename = apr_table_get(r->notes, "Phusion Passenger: original filename"); if (filename == NULL) { return DECLINED; } else { prepareRequest(r, config, filename); /* Always return declined in order to let other modules' * hooks run, regardless of what prepareRequest()'s * result is. */ return DECLINED; } } } else { return DECLINED; } } /** * The default .htaccess provided by on Rails on Rails (that is, before version 2.1.0) * has the following mod_rewrite rules in it: * * RewriteEngine on * RewriteRule ^$ index.html [QSA] * RewriteRule ^([^.]+)$ $1.html [QSA] * RewriteCond %{REQUEST_FILENAME} !-f * RewriteRule ^(.*)$ dispatch.cgi [QSA,L] * * As a result, all requests that do not map to a filename will be redirected to * dispatch.cgi (or dispatch.fcgi, if the user so specified). We don't want that * to happen, so before mod_rewrite applies its rules, we save the current state. * After mod_rewrite has applied its rules, undoRedirectionToDispatchCgi() will * check whether mod_rewrite attempted to perform an internal redirection to * dispatch.(f)cgi. If so, then it will revert the state to the way it was before * mod_rewrite took place. */ int saveStateBeforeRewriteRules(request_rec *r) { RequestNote *note = getRequestNote(r); if (note != 0 && hasModRewrite()) { note->handlerBeforeModRewrite = r->handler; note->filenameBeforeModRewrite = r->filename; } return DECLINED; } int undoRedirectionToDispatchCgi(request_rec *r) { RequestNote *note = getRequestNote(r); if (note == 0 || !hasModRewrite()) { return DECLINED; } if (r->handler != NULL && strcmp(r->handler, "redirect-handler") == 0) { // Check whether r->filename looks like "redirect:.../dispatch.(f)cgi" size_t len = strlen(r->filename); // 22 == strlen("redirect:/dispatch.cgi") if (len >= 22 && memcmp(r->filename, "redirect:", 9) == 0 && (memcmp(r->filename + len - 13, "/dispatch.cgi", 13) == 0 || memcmp(r->filename + len - 14, "/dispatch.fcgi", 14) == 0)) { if (note->filenameBeforeModRewrite != NULL) { r->filename = note->filenameBeforeModRewrite; r->canonical_filename = note->filenameBeforeModRewrite; r->handler = note->handlerBeforeModRewrite; } } } return DECLINED; } /** * mod_dir does the following: * If r->filename is a directory, and the URI doesn't end with a slash, * then it will redirect the browser to an URI with a slash. For example, * if you go to http://foo.com/images, then it will redirect you to * http://foo.com/images/. * * This behavior is undesired. Suppose that there is an ImagesController, * and there's also a 'public/images' folder used for storing page cache * files. Then we don't want mod_dir to perform the redirection. * * So in startBlockingModDir(), we temporarily change some fields in the * request structure in order to block mod_dir. In endBlockingModDir() we * revert those fields to their old value. */ int startBlockingModDir(request_rec *r) { RequestNote *note = getRequestNote(r); if (note != 0 && hasModDir()) { note->oldFileType = r->finfo.filetype; r->finfo.filetype = APR_NOFILE; } return DECLINED; } int endBlockingModDir(request_rec *r) { RequestNote *note = getRequestNote(r); if (note != 0 && hasModDir()) { r->finfo.filetype = note->oldFileType; } return DECLINED; } /** * mod_autoindex will try to display a directory index for URIs that map to a directory. * This is undesired because of page caching semantics. Suppose that a Rails application * has an ImagesController which has page caching enabled, and thus also a 'public/images' * directory. When the visitor visits /images we'll want the request to be forwarded to * the Rails application, instead of displaying a directory index. * * So in this hook method, we temporarily change some fields in the request structure * in order to block mod_autoindex. In endBlockingModAutoIndex(), we restore the request * structure to its former state. */ int startBlockingModAutoIndex(request_rec *r) { RequestNote *note = getRequestNote(r); if (note != 0 && hasModAutoIndex()) { note->handlerBeforeModAutoIndex = r->handler; r->handler = "passenger-skip-autoindex"; } return DECLINED; } int endBlockingModAutoIndex(request_rec *r) { RequestNote *note = getRequestNote(r); if (note != 0 && hasModAutoIndex()) { r->handler = note->handlerBeforeModAutoIndex; } return DECLINED; } int handleRequestWhenInHighPerformanceMode(request_rec *r) { DirConfig *config = getDirConfig(r); if (config->getHighPerformance()) { return handleRequest(r); } else { return DECLINED; } } int handleRequestWhenNotInHighPerformanceMode(request_rec *r) { DirConfig *config = getDirConfig(r); if (config->getHighPerformance()) { return DECLINED; } else { return handleRequest(r); } } }; /****************************************************************** * Below follows lightweight C wrappers around the C++ Hook class. ******************************************************************/ static Hooks *hooks = NULL; static apr_status_t destroy_hooks(void *arg) { try { boost::this_thread::disable_interruption di; boost::this_thread::disable_syscall_interruption dsi; P_DEBUG("Shutting down Phusion Passenger..."); delete hooks; LoggingKit::shutdown(); oxt::shutdown(); hooks = NULL; } catch (const thread_interrupted &) { // Ignore interruptions, we're shutting down anyway. P_TRACE(3, "A system call was interrupted during shutdown of mod_passenger."); } catch (const std::exception &e) { // Ignore other exceptions, we're shutting down anyway. P_TRACE(3, "Exception during shutdown of mod_passenger: " << e.what()); } return APR_SUCCESS; } static int preinit_module(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) { // When reloading Apache, global variables may be left at stale values, // so here we reinitialize them. hooks = NULL; serverConfig = ServerConfig(); return OK; } static int init_module(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { /* * HISTORICAL NOTE: * * The Apache initialization process has the following properties: * * 1. Apache on Unix calls the post_config hook twice, once before detach() and once * after. On Windows it never calls detach(). * 2. When Apache is compiled to use DSO modules, the modules are unloaded between the * two post_config hook calls. * 3. On Unix, if the -X commandline option is given (the 'DEBUG' config is set), * detach() will not be called. * * Because of property #2, the post_config hook is called twice. We initially tried * to avoid this with all kinds of hacks and workarounds, but none of them are * universal, i.e. it works for some people but not for others. So we got rid of the * hacks, and now we always initialize in the post_config hook. */ oxt::initialize(); SystemTime::initialize(); LoggingKit::initialize(); try { hooks = new Hooks(pconf, plog, ptemp, s); apr_pool_cleanup_register(pconf, NULL, destroy_hooks, apr_pool_cleanup_null); return OK; } catch (const boost::thread_interrupted &e) { P_TRACE(2, "A system call was interrupted during mod_passenger " "initialization. Apache might be restarting or shutting " "down. Backtrace:\n" << e.backtrace()); return DECLINED; } catch (const boost::thread_resource_error &e) { struct rlimit lim; string pthread_threads_max; int ret; lim.rlim_cur = 0; lim.rlim_max = 0; /* Solaris does not define the RLIMIT_NPROC limit. Setting it to infinity... */ #ifdef RLIMIT_NPROC getrlimit(RLIMIT_NPROC, &lim); #else lim.rlim_cur = lim.rlim_max = RLIM_INFINITY; #endif #ifdef PTHREAD_THREADS_MAX pthread_threads_max = toString(PTHREAD_THREADS_MAX); #else pthread_threads_max = "unknown"; #endif ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "*** Passenger could not be initialize because a " "threading resource could not be allocated or initialized. " "The error message is:"); fprintf(stderr, " %s\n\n" "System settings:\n" " RLIMIT_NPROC: soft = %d, hard = %d\n" " PTHREAD_THREADS_MAX: %s\n" "\n", e.what(), (int) lim.rlim_cur, (int) lim.rlim_max, pthread_threads_max.c_str()); fprintf(stderr, "Output of 'uname -a' follows:\n"); fflush(stderr); ret = ::system("uname -a >&2"); (void) ret; // Ignore compiler warning. fprintf(stderr, "\nOutput of 'ulimit -a' follows:\n"); fflush(stderr); ret = ::system("ulimit -a >&2"); (void) ret; // Ignore compiler warning. return DECLINED; } catch (const std::exception &e) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "*** Passenger could not be initialized because of this error: %s", e.what()); hooks = NULL; return DECLINED; } } static void child_init(apr_pool_t *pchild, server_rec *s) { if (OXT_LIKELY(hooks != NULL)) { hooks->childInit(pchild, s); } } #define DEFINE_REQUEST_HOOK(c_name, cpp_name) \ static int c_name(request_rec *r) { \ if (OXT_LIKELY(hooks != NULL)) { \ return hooks->cpp_name(r); \ } else { \ return DECLINED; \ } \ } DEFINE_REQUEST_HOOK(prepare_request_when_in_high_performance_mode, prepareRequestWhenInHighPerformanceMode) DEFINE_REQUEST_HOOK(save_original_filename, saveOriginalFilename) DEFINE_REQUEST_HOOK(prepare_request_when_not_in_high_performance_mode, prepareRequestWhenNotInHighPerformanceMode) DEFINE_REQUEST_HOOK(save_state_before_rewrite_rules, saveStateBeforeRewriteRules) DEFINE_REQUEST_HOOK(undo_redirection_to_dispatch_cgi, undoRedirectionToDispatchCgi) DEFINE_REQUEST_HOOK(start_blocking_mod_dir, startBlockingModDir) DEFINE_REQUEST_HOOK(end_blocking_mod_dir, endBlockingModDir) DEFINE_REQUEST_HOOK(start_blocking_mod_autoindex, startBlockingModAutoIndex) DEFINE_REQUEST_HOOK(end_blocking_mod_autoindex, endBlockingModAutoIndex) DEFINE_REQUEST_HOOK(handle_request_when_in_high_performance_mode, handleRequestWhenInHighPerformanceMode) DEFINE_REQUEST_HOOK(handle_request_when_not_in_high_performance_mode, handleRequestWhenNotInHighPerformanceMode) /** * Apache hook registration function. */ void registerHooks(apr_pool_t *p) { static const char * const rewrite_module[] = { "mod_rewrite.c", NULL }; static const char * const dir_module[] = { "mod_dir.c", NULL }; static const char * const autoindex_module[] = { "mod_autoindex.c", NULL }; ap_hook_pre_config(preinit_module, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(init_module, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(child_init, NULL, NULL, APR_HOOK_MIDDLE); // The hooks here are defined in the order that they're called. ap_hook_map_to_storage(prepare_request_when_in_high_performance_mode, NULL, NULL, APR_HOOK_FIRST); ap_hook_map_to_storage(save_original_filename, NULL, NULL, APR_HOOK_LAST); ap_hook_fixups(prepare_request_when_not_in_high_performance_mode, NULL, rewrite_module, APR_HOOK_FIRST); ap_hook_fixups(save_state_before_rewrite_rules, NULL, rewrite_module, APR_HOOK_LAST); ap_hook_fixups(undo_redirection_to_dispatch_cgi, rewrite_module, NULL, APR_HOOK_FIRST); ap_hook_fixups(start_blocking_mod_dir, NULL, dir_module, APR_HOOK_LAST); ap_hook_fixups(end_blocking_mod_dir, dir_module, NULL, APR_HOOK_LAST); ap_hook_handler(handle_request_when_in_high_performance_mode, NULL, NULL, APR_HOOK_FIRST); ap_hook_handler(start_blocking_mod_autoindex, NULL, autoindex_module, APR_HOOK_LAST); ap_hook_handler(end_blocking_mod_autoindex, autoindex_module, NULL, APR_HOOK_FIRST); ap_hook_handler(handle_request_when_not_in_high_performance_mode, NULL, NULL, APR_HOOK_LAST); } } // namespace Apache2Module } // namespace Passenger
Upload File
Create Folder