/*==================================================================== * Copyright (c) 1995-2001 The Apache Group. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" must not be used to * endorse or promote products derived from this software without * prior written permission. * * 5. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . */ static char const *rcsid = "$Id: mod_http_ext.c,v 1.1 2001/10/23 02:03:18 eric Exp $"; /* * mod_http_ext: accounting for HTTP Extensions [1] * * Eric Prud'hommeaux * * Jul 28 2001: started * * [1] http://www.w3.org/Protocols/HTTP/ietf-http-ext/ */ #include "httpd.h" #include "http_config.h" #include "http_log.h" #include "apr_hash.h" #include "apr_strings.h" /* apr_pstr* */ #include "apr_lib.h" /* string parsing */ #include "http_protocol.h" /* for registering hooks */ #include "http_ext.h" #include /* INTERNAL CONSTANTS */ #define REQ_CONTEXT_KEY HTTP_EXT_URI"#mod_http_ext/request-context" #define NS_LEN 2 /* Use APR_HASH_KEY_STRING if ns becomes variable length. */ /* ENABLE_DEBUG_LOGGING - compile in a small amount of extra code to handle * the logALot directive and, surprisingly, log a lot when it's set. * * 1 - add the code * 0 - don't add the code */ #define ENABLE_DEBUG_LOGGING 0 #if ENABLE_DEBUG_LOGGING static int _logReturn (int what, request_rec *r, const char * const where, const char * const why); #define LOGRETURN(WHAT, REQ, WHERE, WHY) return _logReturn(WHAT, REQ, WHERE, WHY) #else #define LOGRETURN(WHAT, REQ, WHERE, WHY) return (WHAT) #endif /* The request context goes into r->pool's user data. */ typedef struct httpExt_reqContext_struct { apr_hash_t *instancesIn; apr_hash_t *instancesOut; int nextNsOut; } httpExt_reqContext_t; typedef struct { /* dir useful in debugging */ char *dir; } httpExt_directory_rec; typedef struct { #if ENABLE_DEBUG_LOGGING int logALot; #endif } httpExt_server_rec; typedef enum {URI, FIELD_NAME} declType_t; typedef struct { declType_t declType; httpExt_notify_t * notify; httpExt_handler_contract contract; } extension_t; typedef struct { char *declName; declType_t declType; extension_t * extension; char *ns; httpExt_handler_contract contract; apr_table_t * headers; apr_table_t * declExts; int mandatory; int connection; } instance_t; static apr_hash_t * Extensions; static void *_httpExt_ap_dir_config(apr_pool_t *p, char *d); static void *_httpExt_ap_dir_merge(apr_pool_t *p, void *basev, void *overridesv); static void *_httpExt_ap_server_config(apr_pool_t *p, server_rec *s); static void *_httpExt_ap_server_merge(apr_pool_t *p, void *basev, void *overridesv); static void _httpExt_ap_register_hooks (apr_pool_t *p); static void _httpExt_ap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s); static int _httpExt_ap_post_read_request(request_rec *r); static const command_rec _httpExt_ap_cmds[]; module AP_MODULE_DECLARE_DATA http_ext_module = { STANDARD20_MODULE_STUFF, _httpExt_ap_dir_config, /* create dir config */ _httpExt_ap_dir_merge, /* merge dir config */ _httpExt_ap_server_config, /* create server config */ _httpExt_ap_server_merge, /* merge server config */ _httpExt_ap_cmds, /* command table */ _httpExt_ap_register_hooks /* register hooks */ }; /* COMMAND HANDLERS */ static const command_rec _httpExt_ap_cmds[] = { #if ENABLE_DEBUG_LOGGING AP_INIT_FLAG("httpExt_logALot", cmd_server_set_flag_slot, (void *) XtOffsetOf(httpExt_server_rec, logALot), RSRC_CONF, "dump copious status to the error log"), #endif { NULL } }; /* SUPPORT FUNCTIONS */ #if ENABLE_DEBUG_LOGGING /* _logReturn - log messages and return first parameter. */ static int _logReturn (int what, request_rec *r, const char * const where, const char * const why) { httpExt_server_rec *srvRec = (httpExt_server_rec *)ap_get_module_config(r->server->module_config, &http_ext_module); if (srvRec->logALot > 0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 999, r->server, "%s returning %d (%s) on %s %s", where, what, why, r->connection->remote_ip, r->the_request); } return what; } #endif /* _httpExt_getReqContext - get request context from request */ static httpExt_reqContext_t * _httpExt_getReqContext (request_rec * r) { httpExt_reqContext_t *ctx; apr_pool_userdata_get((void **)&ctx, REQ_CONTEXT_KEY, r->pool); assert(ctx != NULL); return ctx; } /* _httpExt_genExtensionHeaders - generate HTTP extension headers */ static apr_status_t _httpExt_genExtensionHeaders (ap_filter_t *f, apr_bucket_brigade *b) { request_rec *r = f->r; httpExt_reqContext_t *ctx = _httpExt_getReqContext(r); const char * const names[2][2] = {{"Opt", "Man"}, {"C-Opt", "C-Man"}}; char *out[2][2] = {{NULL, NULL}, {NULL, NULL}}; apr_hash_index_t *hi; /* Generate header values for {,C-}{Man,Opt}. */ for (hi = apr_hash_first(ctx->instancesOut); hi; hi=apr_hash_next(hi)) { instance_t * instance; char * name; apr_hash_this(hi, (const void **)&name, NULL, (void **)&instance); if (out[instance->connection][instance->mandatory] == NULL) out[instance->connection][instance->mandatory] = apr_pstrcat(r->pool, "\"", name, "\"; ns=", instance->ns, NULL); else out[instance->connection][instance->mandatory] = apr_pstrcat(r->pool, out[instance->connection][instance->mandatory], "\"", name, "\"; ns=", instance->ns, NULL); } if (out[0][0] != NULL) apr_table_setn(r->headers_out, names[0][0], out[0][0]); if (out[0][1] != NULL) apr_table_setn(r->headers_out, names[0][1], out[0][1]); if (out[1][0] != NULL) apr_table_setn(r->headers_out, names[1][0], out[1][0]); if (out[1][1] != NULL) apr_table_setn(r->headers_out, names[1][1], out[1][1]); return ap_pass_brigade(f->next, b); } /* _httpExt_parseDecl - parse comma-delimited piece of HTTP extension header. * Grammer taken from RFC 2774[1]: * * mandatory = "Man" ":" 1#ext-decl * optional = "Opt" ":" 1#ext-decl * c-mandatory = "C-Man" ":" 1#ext-decl * c-optional = "C-Opt" ":" 1#ext-decl * * ext-decl = <"> ( absoluteURI | field-name ) <"> [ namespace ] [ decl-extensions ] * * namespace = ";" "ns" "=" header-prefix * header-prefix = 2*DIGIT * * decl-extensions = *( decl-ext ) * decl-ext = ";" token [ "=" ( token | quoted-string ) ] * * [1] ftp://ftp.isi.edu/in-notes/rfc2774.txt */ static const char *_httpExt_parseDecl (request_rec *r, instance_t *result, const char *headerStr, int * pDisposition) { apr_pool_t * p = r->pool; const char * declName; /* Extract declName from between the quotes. */ if (*headerStr != '"') { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "http_ext: could not parse header segment \"%s\"", headerStr); *pDisposition = HTTP_INTERNAL_SERVER_ERROR; return NULL; } declName = ++headerStr; while (*headerStr && *headerStr != '"') ++headerStr; if (*headerStr != '"') { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "http_ext: could not parse header segment \"%s\"", headerStr); *pDisposition = HTTP_INTERNAL_SERVER_ERROR; return NULL; } *headerStr++ = 0; result->ns = NULL; if (strchr(declName, ':')) { result->declName = declName; result->declType = URI; } else { result->declName = apr_pstrdup(p, declName); ap_str_tolower(result->declName); result->declType = FIELD_NAME; } while (*headerStr == ';') { /* Parameters ... */ char *parm; char *cp; char *end; ++headerStr; parm = ap_get_token(p, &headerStr, 1); /* Look for 'var = value' --- and make sure the var is in lcase. */ for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp) { *cp = apr_tolower(*cp); } *cp++ = '\0'; /* Delimit var */ while (*cp && (apr_isspace(*cp) || *cp == '=')) { ++cp; } if (*cp == '"') { ++cp; for (end = cp; (*end && *end != '\n' && *end != '\r' && *end != '\"'); end++); } else { for (end = cp; (*end && !apr_isspace(*end)); end++); } if (*end) { *end = '\0'; /* strip ending quote or return */ } if (result->ns == NULL) { if (parm[0] != 'n' || parm[1] != 's' || parm[2] != 0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "http_ext: could not parse header segment \"%s\"", headerStr); *pDisposition = HTTP_INTERNAL_SERVER_ERROR; return NULL; } result->ns = cp; } else { if (result->declExts == NULL) result->declExts = apr_table_make(p, 3); apr_table_setn(result->declExts, parm, cp); } } if (*headerStr == ',') { ++headerStr; } return headerStr; } /* _httpExt_parseExtHeader - parse HTTP extension header through * iterative calles to _httpExt_parseDecl */ static int _httpExt_parseExtHeader (apr_hash_t * byNs, apr_hash_t * byDeclName, request_rec *r, const char * const headerName, int mandatory, int connection) { const char * headerStr = apr_table_get(r->headers_in, headerName); int ret = OK; if (headerStr == NULL) return OK; while (*headerStr) { instance_t *instance = (instance_t *) apr_pcalloc(r->pool, sizeof(instance_t)); headerStr = _httpExt_parseDecl(r, instance, headerStr, &ret); if (apr_hash_get(byNs, instance->ns, NS_LEN)) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "http_ext: \"%s\" namespace overloaded", instance->ns); return HTTP_INTERNAL_SERVER_ERROR; } instance->mandatory = mandatory; instance->connection = connection; instance->headers = apr_table_make(r->pool, 3); apr_hash_set(byNs, instance->ns, NS_LEN, instance); apr_hash_set(byDeclName, instance->declName, APR_HASH_KEY_STRING, instance); } return ret; } /* APACHE API FUNCTIONS */ static void _httpExt_ap_register_hooks (apr_pool_t *p) { ap_hook_post_config(_httpExt_ap_post_config,NULL,NULL,APR_HOOK_FIRST); ap_hook_post_read_request(_httpExt_ap_post_read_request,NULL,NULL,APR_HOOK_MIDDLE); ap_register_output_filter("httpExt_genExtensionHeaders", _httpExt_genExtensionHeaders, AP_FTYPE_CONTENT); } /* server and directory configurations */ static void *_httpExt_ap_dir_config (apr_pool_t *p, char *d) { httpExt_directory_rec *dirRec = (httpExt_directory_rec *) apr_pcalloc (p, sizeof(httpExt_directory_rec)); /* debugging */ dirRec->dir = apr_pstrdup(p, d); return dirRec; } static void *_httpExt_ap_dir_merge (apr_pool_t *p, void *basev, void *overridesv) { httpExt_directory_rec *merged, *base, *overrides; merged = (httpExt_directory_rec *)apr_pcalloc(p, sizeof(httpExt_directory_rec)); base = (httpExt_directory_rec *)basev; overrides = (httpExt_directory_rec *)overridesv; merged->dir = apr_pstrcat(p, "(", base->dir == NULL ? "" : base->dir, ") -> (", overrides->dir, ")", NULL); return (void *)merged; } static void *_httpExt_ap_server_config (apr_pool_t *p, server_rec *s) { httpExt_server_rec *srvRec = (httpExt_server_rec *)apr_pcalloc(p, sizeof(httpExt_server_rec)); #if ENABLE_DEBUG_LOGGING srvRec->logALot = -1; #endif return srvRec; } static void *_httpExt_ap_server_merge (apr_pool_t *p, void *basev, void *overridesv) { httpExt_server_rec *merged, *base, *overrides; merged = (httpExt_server_rec *)apr_pcalloc(p, sizeof(httpExt_server_rec)); base = (httpExt_server_rec *)basev; overrides = (httpExt_server_rec *)overridesv; #if ENABLE_DEBUG_LOGGING merged->logALot = overrides->logALot == -1 ? base->logALot : overrides->logALot; #endif return merged; } /* _httpExt_ap_post_config - set up index for HTTP extensions. */ static void _httpExt_ap_post_config (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { Extensions = apr_hash_make(p); } /* _httpExt_ap_post_read_request - parse, index and dispatch incoming HTTP * extension headers. Calls httpExt_required_error when mandatory extensions * are not registered. */ static int _httpExt_ap_post_read_request (request_rec *r) { apr_hash_t * byDeclName = apr_hash_make(r->pool); apr_hash_t * byNs = apr_hash_make(r->pool); httpExt_reqContext_t *ctx; int ret; ctx = (httpExt_reqContext_t *)apr_pcalloc(r->pool, sizeof(httpExt_reqContext_t)); if (apr_pool_userdata_set(ctx, REQ_CONTEXT_KEY, apr_pool_cleanup_null, r->pool) != APR_SUCCESS) return HTTP_INTERNAL_SERVER_ERROR; if ((r->main || r->prev) && 0) /* it's an internal request */ return DECLINED; ap_add_output_filter("httpExt_genExtensionHeaders", NULL, r, r->connection); ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "Man", 1, 0); if (ret != OK) return ret; ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "Opt", 0, 0); if (ret != OK) return ret; ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "C-Man", 1, 1); if (ret != OK) return ret; ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "C-Opt", 0, 1); if (ret != OK) return ret; /* Make one pass through the headers looking for matching extensions. * This is where we use the extensions indexed by . */ { apr_array_header_t * hdrs_arr = apr_table_elts(r->headers_in); apr_table_entry_t * hdrs = (apr_table_entry_t *) hdrs_arr->elts; int i; for (i = 0; i < hdrs_arr->nelts; ++i) { char * prefix = apr_pstrdup(r->pool, hdrs[i].key); char * value, * key; instance_t * instance = NULL; if (prefix == NULL || !isdigit(prefix[0]) || !isdigit(prefix[1]) || prefix[2] != '-') continue; key = prefix + 3; prefix[2] = 0; instance = (instance_t *)apr_hash_get(byNs, prefix, NS_LEN); if (instance == NULL) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "http_ext: \"%s\" namespace not found", prefix); return HTTP_INTERNAL_SERVER_ERROR; } value = apr_pstrdup(r->pool, hdrs[i].val); apr_table_setn(instance->headers, key, value); } } ctx->instancesIn = byDeclName; ctx->nextNsOut = 0; ctx->instancesOut = apr_hash_make(r->pool); /* Now that we have the headers, call notify for each instance. */ { apr_hash_index_t *hi; /* Walk list of instances. */ for (hi = apr_hash_first(byNs); hi; hi=apr_hash_next(hi)) { instance_t * instance; extension_t * extension; apr_hash_this(hi, NULL, NULL, (void **)&instance); extension = (extension_t *)apr_hash_get(Extensions, instance->declName, APR_HASH_KEY_STRING); if (extension == NULL) { if (instance->mandatory) /* No handler for mandatory extension so abort. */ return httpExt_required_error(r, instance->declName, instance->ns); } else if (extension->notify) { /* Call extension's notify */ instance->contract = (*extension->notify)(r, instance->declName, instance->headers, instance->declExts); } } } return DECLINED; } /* HTTP EXTENSIONS API FUNCTIONS */ /* httpExt_register_extension - register an HTTP extension module. This is * called at post config time: * * static void myExt_register_hooks (apr_pool_t *p) * { * static const char * const listOfOne[]={ "mod_http_ext.c", NULL }; * * ap_hook_post_config(myExt_post_config, * listOfOne, NULL, APR_HOOK_MIDDLE); * } * static void myExt_post_config (apr_pool_t *p, apr_pool_t *plog, * apr_pool_t *ptemp, server_rec *s) * { * httpExt_register_extension(p, MYEXT_URI, * &myExt_notify, HTTP_EXT_EXCLUSIVE); * } */ httpExt_error_code httpExt_register_extension (apr_pool_t *p, const char * const declName, httpExt_notify_t * notify, httpExt_handler_contract contract) { const char * lDeclName; declType_t declType; extension_t * extension; if (ap_strchr((char *)declName, ':')) { lDeclName = declName; declType = URI; } else { lDeclName = apr_pstrdup(p, declName); ap_str_tolower((char *)lDeclName); declType = FIELD_NAME; } extension = (extension_t *)apr_hash_get(Extensions, lDeclName, APR_HASH_KEY_STRING); if (extension == NULL || extension->contract == HTTP_EXT_NAK) { extension = (extension_t *) apr_pcalloc(p, sizeof(extension_t)); extension->declType = declType; extension->notify = notify; apr_hash_set(Extensions, lDeclName, APR_HASH_KEY_STRING, extension); return HTTP_EXT_OK; } else { if (extension->contract == HTTP_EXT_SHARED && contract == HTTP_EXT_SHARED) /* !!! need to stack them. */ return HTTP_EXT_OK; return HTTP_EXT_CONFLICT; } } /* httpExt_get_headers_in - returns a table of headers associated with declName: * * apr_table_t * hIn = httpExt_get_headers_in (request_rec *r, MYEXT_URI); */ apr_table_t * httpExt_get_headers_in (request_rec *r, const char * const declName) { httpExt_reqContext_t *ctx = _httpExt_getReqContext(r); apr_hash_t *instancesIn = ctx->instancesIn; instance_t * instance = (instance_t *)apr_hash_get(instancesIn, declName, APR_HASH_KEY_STRING); return instance == NULL ? NULL : instance->headers; } /* httpExt_register_response - inform HTTP extensions engine that a module * will be using HTTP extensions in a response. * * httpExt_register_response(r, MYEXT_URI, HTTP_EXT_EXCLUSIVE, isMandatory, * isConnectionExt, myHeaders, NULL, &httpExt_context); */ httpExt_error_code httpExt_register_response (request_rec *r, const char * const declName, httpExt_handler_contract contract, int mandatory, int connection, apr_table_t * headers, apr_table_t * declExts, void **pContext) { httpExt_reqContext_t *ctx = _httpExt_getReqContext(r); instance_t * instance; instance = (instance_t *)apr_hash_get(ctx->instancesOut, declName, APR_HASH_KEY_STRING); if (instance == NULL) { instance = (instance_t *) apr_pcalloc(r->pool, sizeof(instance_t)); apr_hash_set(ctx->instancesOut, declName, APR_HASH_KEY_STRING, instance); instance->mandatory = mandatory; instance->connection = connection; instance->headers = headers; instance->declExts = declExts; instance->contract = contract; instance->ns = apr_pcalloc(r->pool, 3); sprintf(instance->ns, "%02d", ctx->nextNsOut++); if (headers) { apr_array_header_t * hdrs_arr = apr_table_elts(headers); apr_table_entry_t * hdrs = (apr_table_entry_t *) hdrs_arr->elts; int i; for (i = 0; i < hdrs_arr->nelts; ++i) { httpExt_set_header_out(instance, r, hdrs[i].key, hdrs[i].val); } } } else { /* !!! Ugh, what now? */ } if (pContext) *pContext = (void *)instance; return HTTP_EXT_OK; } /* httpExt_set_header_out - set an HTTP extension header. Headers may also * be set when the module calls httpExt_register_response. * * httpExt_set_header_out(context->httpExt_context, r, "My-ext-header", val) */ void httpExt_set_header_out (void * context, request_rec * r, const char * const key, const char * const value) { instance_t * instance = (instance_t *) context; char * headerOut = apr_pstrcat(r->pool, instance->ns, "-", key, NULL); apr_table_setn(r->headers_out, headerOut, value); } /* ERROR-REPORTING HELPER FUNCTIONS */ int httpExt_header_error (request_rec *r, const char * const name, const char * const value) { LOGRETURN(HTTP_BAD_REQUEST, r, name, value); } int httpExt_parameter_error (request_rec *r, const char * const name, const char * const value) { LOGRETURN(HTTP_BAD_REQUEST, r, name, value); } int httpExt_required_error (request_rec *r, const char * const declName, const char * const ns) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server, "http_ext: mandatory \"%s\" (%s) instance not met", declName, ns); return HTTP_NOT_EXTENDED; } #if 0 modules/http/http_protocol.c terminate_header #endif