/* Copyright 2006 Joachim Zobel . * * Licensed 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. */ /* * This is an implementation of XInclude. It is however not a full * implementaion. Only local URLs (as by SSIs virtual) can be * included. XPointers are not supported. * * It requires mod_expat since it processes SAX buckets and mod_expat * is their only source by now. For compiling the header buckets_sax.h * from mod_expat is required. * It is compiled and installed as expected with * /usr/local/apache2/bin/apxs -i -c mod_xi.c * * If you include with parse="xml", the input is converted into SAX * buckets an can be processed by other SAX filters. * If you include with parse="text", the input remains as is and * SAX filter won't touch it. */ #include #include #include #include #include #include #include #include module AP_MODULE_DECLARE_DATA xi_module ; #include "frag_buffer.h" #include "buckets_sax.h" typedef struct { /* keep the unified strings */ const char *ns_prefix ; const char *ns_uri ; const char *include ; const char *fallback ; const char *href ; const char *parse ; /* stack for failures */ apr_array_header_t *fails ; /* counter for fallbacks */ int cnt_fallback ; } xi_ctx ; static const char NS_XI[] = "http://www.w3.org/2001/XInclude" ; /** * Prepends a filter to the given one. * @param f - The filter * @param fname - The filter name to prepend * @return The new filter. */ static ap_filter_t *xi_filter_prepend(ap_filter_t *f, const char *fname) { ap_filter_t *frtn = apr_pcalloc(f->r->pool, sizeof(ap_filter_t)) ; frtn->ctx = NULL ; frtn->next = f ; frtn->r = f->r ; frtn->c = f->c ; frtn->frec = ap_get_output_filter_handle(fname) ; return frtn ; } /* * include attributes */ typedef struct { const xml_char_t *href ; const xml_char_t *parse ; } inc_attr_t ; /** * This function does the include. * @param uri - will be included * @param f - The filter * @param pbb - The bucket brigade, where new buckets are appended. * The function passes this upwards and create a new one. */ static int xi_include(const inc_attr_t *ia, ap_filter_t *f, apr_bucket_brigade **pbb, apr_bucket *b) { request_rec *r = f->r ; request_rec *rr = NULL ; int rv = APR_SUCCESS ; apr_bucket_brigade *bb = *pbb ; /* Prepare the subrequest filter chain */ ap_filter_t *fsub = xi_filter_prepend(f->next, "xi_inc_pp") ; if (!ia->parse || strcmp(ia->parse, "xml")==0) { fsub = xi_filter_prepend(fsub, "expat") ; } else if (strcmp(ia->parse, "text")!=0) { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "Invalid include parse attribute \"%s\".", ia->parse) ; } if (!fsub) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to build the subrequest filter chain.") ; } /* split the brigade at the current bucket */ *pbb = apr_brigade_split(bb, b) ; /* and pass the first part */ rv = ap_pass_brigade(f->next, bb) ; if (rv != APR_SUCCESS) { return rv ; } /* do the subrequest */ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Will subrequest \"%s\".", ia->href) ; rr = ap_sub_req_lookup_uri(ia->href, r, fsub) ; if (rr->status == HTTP_OK) { if (rv = ap_run_sub_req(rr)) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "run include href=\"%s\" failed. Attempting fallback", ia->href) ; } } else { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "lookup include href=\"%s\" failed with %d. Attempting fallback", ia->href, rr->status) ; rv = rr->status ; } ap_destroy_sub_req(rr) ; return rv ; } /** * This function gets the include attributes. * @param se - The start tag * @param fctx - The context holding the unified names * @return The attribute values. */ static inc_attr_t get_inc_attr(const start_elt_t *se, const xi_ctx *fctx) { const attr_t *attr = NULL ; inc_attr_t rtn = {NULL, NULL}; /* search for href, parse */ for (attr=se->atts; attr->name.name; attr++) { if (!attr->name.uri || attr->name.uri==fctx->ns_uri) { if (attr->name.name == fctx->href) { rtn.href = attr->value ; } else if (attr->name.name == fctx->parse) { rtn.parse = attr->value ; } } } return rtn ; } /** * This function does the actual processing, while xi_filter does the bucket * handling. * @param f - The filter * @param pbb - The _next_ bucket brigade, where new buckets are appended. * The function may pass this upwards and create a new one. * @param b - The bucket to process */ static apr_status_t xi_process(ap_filter_t *f, apr_bucket_brigade **pbb, apr_bucket *b) { bucket_sax *bs = b->data ; request_rec *r = f->r ; xi_ctx *fctx = f->ctx ; apr_status_t rv = DECLINED ; /* We are in a fallback section without a failure */ const int suppress = (fctx->fails->nelts < fctx->cnt_fallback) ; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "xi_process with bucket %d.",bs->which) ; switch (bs->which) { case START_ELT: { start_elt_t *se = bs->event ; if (fctx->ns_uri && fctx->ns_uri == se->name.uri) { /* Include */ if (!suppress && se->name.name == fctx->include) { inc_attr_t ia = get_inc_attr(se, fctx) ; /* include */ if (ia.href) { if (APR_SUCCESS != xi_include(&ia, f, pbb, b)) { se_id_t *se_id = apr_array_push(fctx->fails) ; *se_id = se->se_id ; } } else { ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "include tag without href attribute.") ; } } if (se->name.name == fctx->fallback) { fctx->cnt_fallback++; } /* remove SAX bucket */ rv = OK ; } } break ; case END_ELT: { end_elt_t *ee = bs->event ; if (fctx->ns_uri && fctx->ns_uri == ee->name.uri) { /* Include */ if (!suppress && ee->name.name == fctx->include) { int pos = fctx->fails->nelts - 1 ; se_id_t *se_id = (se_id_t *)fctx->fails->elts + pos ; if (pos >= 0 && *se_id == -ee->se_id) { /* This closes a failed include, which we * * remove from the stack. */ apr_array_pop(fctx->fails) ; } } if (ee->name.name == fctx->fallback) { fctx->cnt_fallback--; } /* remove SAX bucket */ rv = OK ; } } break; case START_NS: { start_ns_t *sn = bs->event ; if (strcmp(sn->uri, NS_XI) == 0) { /* We keep prefix + uri, because they are unified */ fctx->ns_prefix = sn->prefix ; fctx->ns_uri = sn->uri ; /* We lookup the unified names for perf. */ fctx->include = sax_unify_name(r->pool, bs->bctx->unq.name, "include") ; fctx->fallback = sax_unify_name(r->pool, bs->bctx->unq.name, "fallback") ; fctx->href = sax_unify_name(r->pool, bs->bctx->unq.name, "href") ; fctx->parse = sax_unify_name(r->pool, bs->bctx->unq.name, "parse") ; /* remove SAX bucket */ rv = OK ; } } break ; case END_NS: { end_ns_t *en = bs->event ; if (fctx->ns_prefix == en->prefix) { /* remove SAX bucket */ rv = OK ; } } break ; case PROC_INSTR: break ; case START_CD: break ; case END_CD: break ; case CHARACTER: break ; case DEFAULT: break ; case WHITE: break ; case COMMENT: break ; case XML_DECL: break ; } return suppress?OK:rv ; } /***************************************************************************** * Module Handlers *****************************************************************************/ /* * xi_filter_init */ static int xi_filter_init(ap_filter_t* f) { apr_pool_t *pool = f->r->pool ; xi_ctx *fctx = f->ctx = apr_pcalloc(pool, sizeof(xi_ctx)) ; memset(fctx, 0, sizeof(xi_ctx)); fctx->fails = apr_array_make(pool, 3, sizeof(se_id_t)) ; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "xi_filter_init called."); f->ctx = fctx ; return OK ; } /* * xi_filter */ static int xi_filter(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket *b ; apr_status_t rv = APR_SUCCESS ; // apr_bucket_brigade *bbnx = // apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc) ; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "xi_filter called."); for ( b = APR_BRIGADE_FIRST(bb) ; b != APR_BRIGADE_SENTINEL(bb) ; b = APR_BUCKET_NEXT(b) ) { if (strcmp(b->type->name, "SAX") == 0) { const int rp = xi_process(f, &bb, b) ; if (rp == OK) { /* Done with it */ APR_BUCKET_REMOVE(b) ; } } } /* We are done, so we pass the brigade */ rv = ap_pass_brigade(f->next, bb) ; return rv ; } /* * xi_subrequest_postprocess */ static int xi_include_postprocess(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket *b ; apr_status_t rv = APR_SUCCESS ; // apr_bucket_brigade *bbnx = // apr_brigade_create(f->r->pool, f->r->connection->bucket_alloc) ; ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, f->r, "xi_include_postprocess called."); for ( b = APR_BRIGADE_FIRST(bb) ; b != APR_BRIGADE_SENTINEL(bb) ; b = APR_BUCKET_NEXT(b) ) { if (strcmp(b->type->name, "SAX") == 0) { bucket_sax *bs = b->data ; /* We search for the XML decl. */ if (bs->which == XML_DECL) { APR_BUCKET_REMOVE(b) ; break ; } /* Only (empty) char buckets can be before it */ if (bs->which != CHARACTER) { break ; } } else { break; } } /* Remove EOS */ b = APR_BRIGADE_LAST(bb) ; if (APR_BUCKET_IS_EOS(b)) { APR_BUCKET_REMOVE(b) ; } /* We are done, so we pass the brigade */ rv = ap_pass_brigade(f->next, bb) ; return rv ; } /***************************************************************************** * The usual module stuff *****************************************************************************/ static void xi_hooks(apr_pool_t* p) { ap_register_output_filter("xi", xi_filter, xi_filter_init, AP_FTYPE_RESOURCE) ; ap_register_output_filter("xi_inc_pp", xi_include_postprocess, NULL, AP_FTYPE_RESOURCE) ; } module AP_MODULE_DECLARE_DATA xi_module = { STANDARD20_MODULE_STUFF, NULL, NULL, NULL, NULL, NULL, xi_hooks } ;