Your IP : 3.135.249.119
/*
* Phusion Passenger - https://www.phusionpassenger.com/
* Copyright (c) 2010-2018 Phusion Holding B.V.
*
* "Passenger", "Phusion Passenger" and "Union Station" are registered
* trademarks of Phusion Holding 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;
}
int reportBusyException(request_rec *r) {
ap_custom_response(r, HTTP_SERVICE_UNAVAILABLE,
"This website is too busy right now. Please try again later.");
return HTTP_SERVICE_UNAVAILABLE;
}
/**
* 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["prestart_urls"] = strsetToJson(serverConfig.prestartURLs);
config["admin_panel_url"] = nonEmptyString(serverConfig.adminPanelUrl);
config["admin_panel_auth_type"] = nonEmptyString(serverConfig.adminPanelAuthType);
config["admin_panel_username"] = nonEmptyString(serverConfig.adminPanelUsername);
config["admin_panel_password"] = nonEmptyString(serverConfig.adminPanelPassword);
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