/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* $Rev$ $Date$ */ #ifndef tuscany_httpd_hpp #define tuscany_httpd_hpp /** * HTTPD module implementation functions. */ extern "C" { #include #include #include #define APR_WANT_STRFUNC #include #include #include // Hack to workaround compile error with CLang/LLVM #undef strtoul // Hack to workaround compile error with HTTPD 2.3.8 #define new new_ #include #undef new #include #include #include // Ignore conversion warnings in HTTPD 2.3.15 header #ifdef WANT_MAINTAINER_WARNINGS #ifndef __clang__ #pragma GCC diagnostic ignored "-Wconversion" #endif #endif #include // Re-enable conversion warnings #ifdef WANT_MAINTAINER_WARNINGS #ifndef __clang__ #pragma GCC diagnostic warning "-Wconversion" #endif #endif // Hack to workaround compile error with HTTPD 2.3.8 #define aplog_module_index aplog_module_index = 0 #include #undef aplog_module_index #undef APLOG_MODULE_INDEX #define APLOG_MODULE_INDEX (aplog_module_index ? *aplog_module_index : APLOG_NO_MODULE) #include #include #include #include #include #include #include #include #include #include } #include "string.hpp" #include "stream.hpp" #include "sstream.hpp" #include "list.hpp" #include "value.hpp" #include "monad.hpp" #include "http.hpp" namespace tuscany { namespace httpd { /** * Returns a server-scoped module configuration. */ template void* makeServerConf(apr_pool_t* p, server_rec* s) { return new (gc_new(p)) C(p, s); } template const C& serverConf(const request_rec* const r, const module* const mod) { return *(C*)ap_get_module_config(r->server->module_config, mod); } template C& serverConf(const server_rec* const s, const module* const mod) { return *(C*)ap_get_module_config(s->module_config, mod); } template C& serverConf(const cmd_parms* const cmd, const module* const mod) { return *(C*)ap_get_module_config(cmd->server->module_config, mod); } /** * Returns a directory-scoped module configuration. */ template void* makeDirConf(apr_pool_t *p, char* d) { return new (gc_new(p)) C(p, d); } template const C& dirConf(const request_rec* const r, const module* const mod) { return *(C*)ap_get_module_config(r->per_dir_config, mod); } template C& dirConf(const void* const c) { return *(C*)c; } /** * Returns a request-scoped module configuration. */ template C& makeRequestConf(const request_rec* const r, const module* const mod) { C* const c = new (gc_new(r->pool)) C(r->pool, r); ap_set_module_config(r->request_config, mod, c); return *c; } template C& requestConf(const request_rec* const r, const module* const mod) { C* const c = (C*)ap_get_module_config(r->request_config, mod); if (c == NULL) return makeRequestConf(r, mod); return *c; } /** * Return the host name for a server. */ const string hostName(const server_rec* const s, const string& def = "localhost") { return s->server_hostname != NULL? s->server_hostname : def; } /** * Return the host name from an HTTP request. */ const string hostName(request_rec* const r, const string& def = "localhost") { const char* const fh = apr_table_get(r->headers_in, "X-Forwarded-Server"); if (fh != NULL) return fh; const char* const h = ap_get_server_name(r); return h != NULL? h : (r->server->server_hostname != NULL? r->server->server_hostname : def); } /** * Convert a host name to a realm. */ const string realm(const string& host) { const string pre = substr(host, 0, 4); return pre == "www." || pre == "ww1." || pre == "ww2." || pre == "ww3."? substr(host, 4) : host; } /** * Return the protocol scheme for a server. */ const string scheme(const server_rec* const s, const string& def = "http") { return s->server_scheme != NULL? s->server_scheme : def; } /** * Return the protocol scheme from an HTTP request. */ const string scheme(const request_rec* const r, const string& def = "http") { const char* const fs = apr_table_get(r->headers_in, "X-Forwarded-HTTPS"); if (fs != NULL) return !strcmp(fs, "on")? "https" : "http"; return r->server->server_scheme != NULL? r->server->server_scheme : def; } /** * Return the port number for a server. */ const int port(const server_rec* const s, const int def = 80) { return s->port != 0? s->port : def; } /** * Return the port number from an HTTP request. */ const int port(const request_rec* const r, const int def = 80) { const char* const fp = apr_table_get(r->headers_in, "X-Forwarded-Port"); if (fp != NULL) return atoi(fp); const int p = ap_get_server_port(r); return p != 0? p : def; } /** * Return the name of a server. */ const string serverName(server_rec* const s, const string& def = "localhost") { ostringstream n; const string sc = scheme(s); const string h = hostName(s, def); const int p = port(s, sc == "https"? 443 : 80); n << sc << "://" << h; if (!((sc == "http" && p == 80) || (sc == "https" && p == 443))) n << ":" << p; n << (s->path != NULL? string(s->path, s->pathlen) : emptyString); return str(n); } /** * Determine the name of a server from an HTTP request. */ const string serverName(request_rec* const r, const string& def = "localhost") { ostringstream n; const string s = scheme(r); const string h = hostName(r, def); const int p = port(r, s == "https"? 443 : 80); n << s << "://" << h; if (!((s == "http" && p == 80) || (s == "https" && p == 443))) n << ":" << p; n << (r->server->path != NULL? string(r->server->path, r->server->pathlen) : emptyString); return str(n); } /** * Return true if a request is targeting a virtual host. */ const bool isVhostRequest(const server_rec* const s, const string& d, request_rec* const r) { const string rh = hostName(r); return rh != hostName(s) && http::topDomain(rh) == d; } /** * Return the content type of a request. */ const string contentType(const request_rec* const r) { const char* ct = apr_table_get(r->headers_in, "Content-Type"); if (ct == NULL) return emptyString; return ct; } /** * Return the cookie header of a request. */ const string cookie(const request_rec* const r) { const char* c = apr_table_get(r->headers_in, "Cookie"); if (c == NULL) return emptyString; return c; } /** * Return the remaining part of a uri after the given path (aka the path info.) */ const list pathInfo(const list& uri, const list& path) { if (isNull(path)) return uri; return pathInfo(cdr(uri), cdr(path)); } /** * Convert a URI to an absolute URL. */ const string url(const string& uri, request_rec* const r) { if (contains(uri, "://")) return uri; ostringstream n; const string s = scheme(r); const string h = hostName(r, "localhost"); const int p = port(r, s == "https"? 443 : 80); n << s << "://" << h; if (!((s == "http" && p == 80) || (s == "https" && p == 443))) n << ":" << p; n << uri; return str(n); } /** * Convert a URI and a path to an absolute URL. */ const string url(const string& uri, const list& p, request_rec* const r) { return url(uri + (string)path(p), r); } /** * Escape a URI. */ const char escape_c2x[] = "0123456789ABCDEF"; const string escape(const string& uri) { debug(uri, "httpd::escape::uri"); char* const copy = (char*)apr_palloc(gc_current_pool(), 3 * length(uri) + 3); const unsigned char* s = (const unsigned char *)c_str(uri); unsigned char* d = (unsigned char*)copy; unsigned c; while ((c = *s)) { if (apr_isalnum(c) || c == '_') *d++ = (unsigned char)c; else if (c == ' ') *d++ = '+'; else { *d++ = '%'; *d++ = escape_c2x[c >> 4]; *d++ = escape_c2x[c & 0xf]; } ++s; } *d = '\0'; debug(copy, "httpd::escape::result"); return copy; } /** * Unescape a URI. */ const string unescape(const string& uri) { debug(uri, "httpd::unescape::uri"); char* const b = const_cast(c_str(string(c_str(uri)))); ap_unescape_url(b); debug(b, "httpd::unescape::result"); return b; } /** * Unescape a list of key of value pairs representing query args. */ const value unescapeArg(const value& a) { return mklist(car(a), unescape(cadr(a))); } const list unescapeArgs(const list& args) { debug(args, "httpd::unescape::args"); const list uargs = map(unescapeArg, args); debug(uargs, "httpd::unescape::result"); return uargs; } /** * Returns a list of key value pairs from the args in a query string. */ const value queryArg(const string& s) { debug(s, "httpd::queryArg::string"); const list t = tokenize("=", s); if (isNull(cdr(t))) return mklist(c_str(car(t)), emptyString); return mklist(c_str(car(t)), cadr(t)); } const string fixupQueryArgs(const string& a) { const list t = tokenize("?", a); if (isNull(t) || isNull(cdr(t))) return a; return join("&", t); } const list queryArgs(const string& a) { return map(queryArg, tokenize("&", fixupQueryArgs(a))); } /** * Returns a list of key value pairs from the args in an HTTP request. */ const list queryArgs(const request_rec* const r) { if (r->args == NULL) return nilListValue; return queryArgs(r->args); } /** * Converts the args received in a POST to a list of key value pairs. */ const list postArgs(const list& a) { if (isNull(a)) return nilListValue; const list l = car(a); return cons(l, postArgs(cdr(a))); } /** * Setup the HTTP read policy. */ const int setupReadPolicy(request_rec* const r) { const int rc = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK); if(rc != OK) return rc; ap_should_client_block(r); if(r->read_chunked == true && r->remaining == 0) r->chunked = true; //apr_table_setn(r->headers_out, "Connection", "close"); return OK; } /** * Read the content of an HTTP request. */ const list read(request_rec* const r) { char b[1024]; const size_t n = ap_get_client_block(r, b, sizeof(b)); if (n <= 0) return list(); return cons(string(b, n), read(r)); } /** * Write an HTTP result. */ const failable writeResult(const failable >& ls, const string& ct, request_rec* const r) { if (!hasContent(ls)) return mkfailure(ls); ostringstream os; write(content(ls), os); const string ob(str(os)); // Make sure browsers come back and check for updated dynamic content apr_table_set(r->headers_out, "Cache-Control", "must-revalidate, max-age=0"); apr_table_set(r->headers_out, "Expires", "Tue, 01 Jan 1980 00:00:00 GMT"); apr_table_set(r->subprocess_env, "private-cache", "1"); // Compute and return an Etag for the returned content const string etag(ap_md5_binary(r->pool, (const unsigned char*)c_str(ob), (int)length(ob))); // Check for an If-None-Match header and just return a 304 not-modified status // if the Etag matches the Etag presented by the client, to save bandwith const char* match = apr_table_get(r->headers_in, "If-None-Match"); apr_table_setn(r->headers_out, "ETag", apr_pstrdup(r->pool, c_str(etag))); if (match != NULL && etag == match) { r->status = HTTP_NOT_MODIFIED; debug(r->status, "httpd::writeResult::status"); return OK; } debug(r->status, "httpd::writeResult::status"); debug(ct, "httpd::writeResult::contentType"); debug(ob, "httpd::writeResult::content"); ap_set_content_type(r, apr_pstrdup(r->pool, c_str(ct))); ap_rwrite(c_str(ob), (int)length(ob), r); return OK; } /** * Report a request execution status. */ const int reportStatus(const failable& rc) { debug(rc, "httpd::reportStatus::rc"); if (!hasContent(rc)) { const int r = rcode(rc); return r == -1 ? HTTP_INTERNAL_SERVER_ERROR : r; } return content(rc); } /** * Convert a value to an HTTPD request struct. */ request_rec* request(const value& v) { return (request_rec*)(long)(double)v; } /** * Convert an HTTPD request struct to a value. */ const value requestValue(const request_rec* const r) { return value((double)(long)r); } /** * Update request filters in an HTTPD redirect request. * Similar to httpd/modules/http/http_request.c::update_r_in_filters. */ const bool internalUpdateFilters(ap_filter_t *f, const request_rec* const from, request_rec* const to) { while(f) { if(f->r == from) f->r = to; f = f->next; } return true; } /** * Rename original env in an HTTPD redirect request. * Similar to httpd/modules/http/http_request.c::rename_original_env. */ apr_table_t* const internalRenameOriginalEnv(apr_pool_t* const p, apr_table_t* const t) { const apr_array_header_t *env_arr = apr_table_elts(t); const apr_table_entry_t *elts = (const apr_table_entry_t *) (void*)env_arr->elts; apr_table_t *nt = apr_table_make(p, env_arr->nalloc); int i; for(i = 0; i < env_arr->nelts; ++i) { if (!elts[i].key) continue; apr_table_setn(nt, apr_pstrcat(p, "REDIRECT_", elts[i].key, NULL), elts[i].val); } return nt; } /** * Create an HTTPD internal redirect request. * Similar to httpd/modules/http/http_request.c::internal_internal_redirect. */ const failable internalRedirectRequest(const string& nr_uri, request_rec* const r) { if (ap_is_recursion_limit_exceeded(r)) return mkfailure("Redirect recursion limit exceeded", HTTP_INTERNAL_SERVER_ERROR); // Create a new request request_rec* nr = (request_rec*)apr_pcalloc(r->pool, sizeof(request_rec)); nr->connection = r->connection; nr->server = r->server; nr->pool = r->pool; nr->method = r->method; nr->method_number = r->method_number; nr->allowed_methods = ap_make_method_list(nr->pool, 2); ap_parse_uri(nr, apr_pstrdup(nr->pool, c_str(nr_uri))); nr->parsed_uri.port_str = r->parsed_uri.port_str; nr->parsed_uri.port = r->parsed_uri.port; nr->filename = apr_pstrdup(nr->pool, c_str(string("/redirected:") + nr_uri)); nr->request_config = ap_create_request_config(r->pool); nr->per_dir_config = r->server->lookup_defaults; nr->prev = r; r->next = nr; nr->useragent_addr = r->useragent_addr; nr->useragent_ip = r->useragent_ip; // Run create request hook ap_run_create_request(nr); // Inherit protocol info from the original request nr->the_request = r->the_request; nr->allowed = r->allowed; nr->status = r->status; nr->assbackwards = r->assbackwards; nr->header_only = r->header_only; nr->protocol = r->protocol; nr->proto_num = r->proto_num; nr->hostname = r->hostname; nr->request_time = r->request_time; nr->main = r->main; nr->headers_in = r->headers_in; nr->headers_out = apr_table_make(r->pool, 12); if (ap_is_HTTP_REDIRECT(nr->status)) { const char *location = apr_table_get(r->headers_out, "Location"); if (location) apr_table_setn(nr->headers_out, "Location", location); } nr->err_headers_out = r->err_headers_out; nr->subprocess_env = r->subprocess_env; nr->subprocess_env = internalRenameOriginalEnv(r->pool, r->subprocess_env); nr->notes = apr_table_make(r->pool, 5); nr->htaccess = r->htaccess; nr->no_cache = r->no_cache; nr->expecting_100 = r->expecting_100; nr->no_local_copy = r->no_local_copy; nr->read_length = r->read_length; nr->vlist_validator = r->vlist_validator; nr->user = r->user; // Setup input and output filters nr->proto_output_filters = r->proto_output_filters; nr->proto_input_filters = r->proto_input_filters; nr->output_filters = nr->proto_output_filters; nr->input_filters = nr->proto_input_filters; if (nr->main) { ap_filter_t *f, *nextf; nr->output_filters = r->output_filters; f = nr->output_filters; do { nextf = f->next; if (f->r == r && f->frec != ap_subreq_core_filter_handle) { f->r = nr; ap_remove_output_filter(f); } f = nextf; } while(f && f != nr->proto_output_filters); } else { nr->output_filters = nr->proto_output_filters; } internalUpdateFilters(nr->input_filters, r, nr); internalUpdateFilters(nr->output_filters, r, nr); apr_table_setn(nr->subprocess_env, "REDIRECT_STATUS", apr_itoa(r->pool, r->status)); nr->used_path_info = AP_REQ_DEFAULT_PATH_INFO; const int rrc = ap_run_post_read_request(nr); if (rrc != OK && rrc != DECLINED) return mkfailure("Error handling internal redirect", rrc); return nr; } /** * Process an HTTPD internal redirect request. * Similar to httpd/modules/http/http_request.c::ap_internal_redirect. */ const int internalRedirect(request_rec* const nr) { int status = ap_run_quick_handler(nr, 0); if (status == DECLINED) { status = ap_process_request_internal(nr); if (status == OK) status = ap_invoke_handler(nr); } if (status != OK) { nr->status = status; return OK; } ap_finalize_request_protocol(nr); return OK; } /** * Create and process an HTTPD internal redirect request. */ const int internalRedirect(const string& uri, request_rec* const r) { debug(uri, "httpd::internalRedirect"); //ap_internal_redirect(c_str(uri), r); //return OK; const failable nr = httpd::internalRedirectRequest(uri, r); if (!hasContent(nr)) return rcode(nr); return httpd::internalRedirect(content(nr)); } /** * Create an HTTPD sub request. * Similar to httpd/server/request.c::make_sub_request */ const failable internalSubRequest(const string& nr_uri, request_rec* const r) { if (ap_is_recursion_limit_exceeded(r)) return mkfailure("Redirect recursion limit exceeded", HTTP_INTERNAL_SERVER_ERROR); // Create a new sub pool apr_pool_t *nrp; apr_pool_create(&nrp, r->pool); apr_pool_tag(nrp, "subrequest"); // Create a new POST request request_rec* nr = (request_rec*)apr_pcalloc(nrp, sizeof(request_rec)); nr->connection = r->connection; nr->server = r->server; nr->pool = nrp; nr->method = "POST"; nr->method_number = M_POST; nr->allowed_methods = ap_make_method_list(nr->pool, 2); ap_parse_uri(nr, apr_pstrdup(nr->pool, c_str(nr_uri))); nr->filename = apr_pstrdup(nr->pool, c_str(string("/subreq:") + nr_uri)); nr->request_config = ap_create_request_config(r->pool); nr->per_dir_config = r->server->lookup_defaults; // Inherit some of the protocol info from the parent request nr->the_request = r->the_request; nr->hostname = r->hostname; nr->request_time = r->request_time; nr->allowed = r->allowed; nr->status = HTTP_OK; nr->assbackwards = r->assbackwards; nr->header_only = r->header_only; nr->protocol = const_cast("INCLUDED"); nr->hostname = r->hostname; nr->request_time = r->request_time; nr->main = r; nr->headers_in = apr_table_make(r->pool, 12); nr->headers_out = apr_table_make(r->pool, 12); nr->err_headers_out = apr_table_make(nr->pool, 5); nr->subprocess_env = r->subprocess_env; nr->subprocess_env = apr_table_copy(nr->pool, r->subprocess_env); nr->notes = apr_table_make(r->pool, 5); nr->htaccess = r->htaccess; nr->no_cache = r->no_cache; nr->expecting_100 = r->expecting_100; nr->no_local_copy = r->no_local_copy; nr->read_length = 0; nr->vlist_validator = r->vlist_validator; nr->user = r->user; // Setup input and output filters nr->proto_output_filters = r->proto_output_filters; nr->proto_input_filters = r->proto_input_filters; nr->output_filters = nr->proto_output_filters; nr->input_filters = nr->proto_input_filters; ap_add_output_filter_handle(ap_subreq_core_filter_handle, NULL, nr, nr->connection); // Run create request hook ap_run_create_request(nr); nr->used_path_info = AP_REQ_DEFAULT_PATH_INFO; return nr; } /** * Return an HTTP external redirect request. */ const int externalRedirect(const string& uri, request_rec* const r) { debug(uri, "httpd::externalRedirect"); r->status = HTTP_MOVED_TEMPORARILY; apr_table_setn(r->headers_out, "Location", apr_pstrdup(r->pool, c_str(uri))); apr_table_setn(r->headers_out, "Cache-Control", "no-store"); apr_table_addn(r->err_headers_out, "Cache-Control", "no-store"); r->filename = apr_pstrdup(r->pool, c_str(string("/redirect:/") + uri)); return HTTP_MOVED_TEMPORARILY; } /** * Put a value in the process user data. */ const bool putUserData(const string& k, const void* const v, const server_rec* const s) { apr_pool_userdata_set((const void *)v, c_str(k), apr_pool_cleanup_null, s->process->pool); return true; } /** * Return a user data value. */ const void* const userData(const string& k, const server_rec* const s) { void* v = NULL; apr_pool_userdata_get(&v, c_str(k), s->process->pool); return v; } #ifdef WANT_MAINTAINER_LOG /** * Debug log. */ /** * Log an optional value. */ const char* const debugOptional(const char* const s) { if (s == NULL) return ""; return s; } /** * Log a header */ int debugHeader(unused void* r, const char* const key, const char* const value) { cdebug << " header key: " << key << ", value: " << value << endl; return 1; } /** * Log an environment variable */ int debugEnv(unused void* r, const char* const key, const char* const value) { cdebug << " var key: " << key << ", value: " << value << endl; return 1; } /** * Log a note. */ int debugNote(unused void* r, const char* const key, const char* const value) { cdebug << " note key: " << key << ", value: " << value << endl; return 1; } /** * Log a request. */ const bool debugRequest(request_rec* const r, const string& msg) { const gc_scoped_pool pool; cdebug << msg << ":" << endl; cdebug << " unparsed uri: " << debugOptional(r->unparsed_uri) << endl; cdebug << " uri: " << debugOptional(r->uri) << endl; cdebug << " path info: " << debugOptional(r->path_info) << endl; cdebug << " filename: " << debugOptional(r->filename) << endl; cdebug << " uri tokens: " << pathTokens(r->uri) << endl; cdebug << " args: " << debugOptional(r->args) << endl; cdebug << " server: " << debugOptional(r->server->server_hostname) << endl; cdebug << " protocol: " << debugOptional(r->protocol) << endl; cdebug << " method: " << debugOptional(r->method) << endl; cdebug << " method number: " << r->method_number << endl; cdebug << " content type: " << contentType(r) << endl; cdebug << " content encoding: " << debugOptional(r->content_encoding) << endl; apr_table_do(debugHeader, r, r->headers_in, NULL); cdebug << " user: " << debugOptional(r->user) << endl; cdebug << " auth type: " << debugOptional(r->ap_auth_type) << endl; apr_table_do(debugEnv, r, r->subprocess_env, NULL); apr_table_do(debugNote, r, r->notes, NULL); return true; } #define debug_httpdRequest(r, msg) do { if (debug_islogging()) httpd::debugRequest(r, msg); } while(0) #else #define debug_httpdRequest(r, msg) #endif } } #endif /* tuscany_httpd_hpp */