#include #include #include #include #include #include #define XSLT_FILTER_NAME "XSLT" module AP_MODULE_DECLARE_DATA choices_module ; typedef struct choices_cfg { int choices ; /* flag to turn this module on/off */ apr_hash_t* transforms ; /* table of "extensions" known to * this server. */ } choices_cfg ; typedef struct choices_transform { const char* ctype ; /* Content-Type for this extension */ const char* xslt ; /* Name of XSLT stylesheet for * this extension. */ } choices_transform ; static int choices_select(request_rec* r) { choices_transform* fmt ; char* ext ; /* first, look up our module configuration */ choices_cfg* cfg = ap_get_module_config(r->per_dir_config, &choices_module) ; if ( ! cfg->choices ) { /* This request has nothing to do with this module */ return DECLINED ; } if ( r->method_number != M_GET ) { /* Other methods are allowed, but this hook isn't interested. */ return DECLINED ; } if ( ! r->filename ) { /* This can't happen. But if it does (e.g. a buggy third-party * module has messed up our request), a server error is better * than a server crash when we dereference a null pointer :-) */ return HTTP_INTERNAL_SERVER_ERROR ; } /* Our request has been mapped to the filesystem, but it may not * match anything that's really there. We can stat it to find out. */ if ( apr_stat(&r->finfo, r->filename, APR_FINFO_SIZE, r->pool) == APR_SUCCESS ) { /* The request maps directly to a file. We don't need to * do anything except serve it as XML */ ap_set_content_type(r, "application/xml;charset=utf-8") ; } else { /* The request doesn't map to a file. We need to check whether we * can map to a file by stripping the "extension" off. * * First, we split the filename. */ ext = strrchr(r->filename, '.') ; if ( ext ) { *ext++ = 0 ; } else { /* No such file and not a name we can parse as an extension */ return HTTP_NOT_FOUND ; /* (1) */ } /* Now we can see whether we have a file we can map */ if ( apr_stat(&r->finfo, r->filename, APR_FINFO_SIZE, r->pool) == APR_SUCCESS ) { /* OK, it's there. Now check whether it's an extension we know */ fmt = apr_hash_get(cfg->transforms, ext, APR_HASH_KEY_STRING) ; if ( fmt ) { /* OK, we have a transform for this extension * Now we set request properties accordingly */ ap_set_content_type(r, fmt->ctype) ; /* this function is exported by mod_transform, and selects * an XSLT transform to run for this request */ mod_transform_set_XSLT(r, fmt->xslt) ; /* Finally, we insert mod_transform in the output chain * The filter name is also exported by mod_transform */ ap_add_output_filter(XSLT_FILTER_NAME, NULL, r, r->connection) ; } else { /* We don't know this extension, so we can't serve it * We can return NOT_FOUND, or implement an error handler * to present the user a list of options. */ return HTTP_MULTIPLE_CHOICES ; /* (2) */ } } else { /* apr_stat failed - there's no underlying file to serve */ return HTTP_NOT_FOUND ; /* (3) */ } } /* OK, we've finished configuring this request */ return OK ; } static int choices_errordoc(request_rec* r) { /* This is an error handler we can use if we return * HTTP_MULTIPLE_CHOICES instead of HTTP_NOT_FOUND at (2) above */ choices_cfg* cfg ; const char* ext ; apr_ssize_t len ; choices_transform* rec ; apr_hash_index_t* ht ; char* p; /* Ignore the request if we're not in an internal redirect */ if ( ! r->prev || ! r->prev->filename ) { return DECLINED ; } /* Insist on being configured before we do anything */ if ( strcmp(r->handler, "choices-errordoc") ) { return DECLINED ; } cfg = ap_get_module_config(r->prev->per_dir_config, &choices_module) ; ap_set_content_type(r, "text/html;charset=ascii") ; /* Now we can print an error page, listing the 'base' (XML) * document and other available variants. * * The base url is in r->prev->uri. We just need to strip extensions. */ p = strchr(r->prev->uri, '.') ; if (p != NULL) *p = 0 ; ap_rprintf(r, "\n" "No such format\n" "

Format not supported

\n" "

mod_chocies on this server is not configured to support " "the requested document format. Available options are:

\n" "" "" "" "\n" "" "\n", r->prev->uri, r->prev->uri) ; /* We already saw how to iterate over a table in Chapter 5. * For a hash table we use a more traditional loop. */ for (ht = apr_hash_first(r->pool, cfg->transforms); ht; ht = apr_hash_next(ht)) { apr_hash_this(ht, (const void**)&ext, &len, (void**)&rec) ; ap_rprintf(r, "\n", rec->ctype, r->prev->uri, ext, rec->ctype, r->prev->uri, ext) ; } ap_rputs("
Document TypeURL
application/xml%s
%s%s.%s
" , r) ; return OK ; } static void* choices_cr_cfg(apr_pool_t* pool, char* x) { choices_cfg* ret = apr_pcalloc(pool, sizeof(choices_cfg)) ; ret->transforms = apr_hash_make(pool) ; return ret ; } static const char* choices_transform_set(cmd_parms* cmd, void* cfg, const char* ext, const char* ctype, const char* xslt) { apr_hash_t* transforms = ((choices_cfg*)cfg)->transforms ; choices_transform* t = apr_palloc(cmd->pool, sizeof(choices_transform)); t->ctype = ctype ; t->xslt = xslt ; apr_hash_set(transforms, ext, APR_HASH_KEY_STRING, t); return NULL; } static const command_rec choices_cmds[] = { AP_INIT_FLAG("Choices", ap_set_flag_slot, (void*)APR_OFFSETOF(choices_cfg, choices), ACCESS_CONF, "Enable document variant selection by extension"), AP_INIT_TAKE3("ChoicesTransform", choices_transform_set, NULL, ACCESS_CONF, "Define content-type and XSLT for an extension"), {NULL} } ; static void choices_hooks(apr_pool_t* pool) { ap_hook_handler(choices_errordoc, NULL, NULL, APR_HOOK_MIDDLE) ; ap_hook_type_checker(choices_select, NULL, NULL, APR_HOOK_FIRST) ; } module AP_MODULE_DECLARE_DATA choices_module = { STANDARD20_MODULE_STUFF, choices_cr_cfg , NULL , NULL , NULL , choices_cmds , choices_hooks } ;