# HG changeset patch # User Igor Sysoev # Date 1217861295 0 # Node ID dad4423ef56a15d256493ee274a489c4cdb2afd3 # Parent fb3b084e7d42d19103b470b85ba51c3f65907ba2 ngx_http_xslt_filter_module diff --git a/auto/feature b/auto/feature --- a/auto/feature +++ b/auto/feature @@ -19,7 +19,9 @@ if test -n "$ngx_feature_name"; then fi if test -n "$ngx_feature_path"; then - ngx_feature_inc_path="-I $ngx_feature_path" + for ngx_temp in $ngx_feature_path; do + ngx_feature_inc_path="$ngx_feature_inc_path -I $ngx_temp" + done fi cat << END > $NGX_AUTOTEST.c diff --git a/auto/lib/conf b/auto/lib/conf --- a/auto/lib/conf +++ b/auto/lib/conf @@ -41,6 +41,10 @@ if [ $USE_ZLIB = YES ]; then . auto/lib/zlib/conf fi +if [ $USE_LIBXSLT = YES ]; then + . auto/lib/libxslt/conf +fi + if [ $USE_PERL = YES ]; then . auto/lib/perl/conf fi diff --git a/auto/lib/libxslt/conf b/auto/lib/libxslt/conf new file mode 100644 --- /dev/null +++ b/auto/lib/libxslt/conf @@ -0,0 +1,78 @@ + +# Copyright (C) Igor Sysoev + + + ngx_feature="libxslt" + ngx_feature_name= + ngx_feature_run=no + ngx_feature_incs="#include + #include + #include + #include + #include + #include " + ngx_feature_path="/usr/include/libxml2" + ngx_feature_libs="-lxml2 -lxslt" + ngx_feature_test="xmlParserCtxtPtr ctxt = NULL; + xsltStylesheetPtr sheet = NULL; + xmlDocPtr doc; + doc = xmlParseChunk(ctxt, NULL, 0, 0); + xsltApplyStylesheet(sheet, doc, NULL);" + . auto/feature + + +if [ $ngx_found = no ]; then + + # FreeBSD port + + ngx_feature="libxslt in /usr/local/" + ngx_feature_path="/usr/local/include/libxml2 /usr/local/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lxml2 -lxslt" + else + ngx_feature_libs="-L/usr/local/lib -lxml2 -lxslt" + fi + + . auto/feature +fi + + +if [ $ngx_found = no ]; then + + # NetBSD port + + ngx_feature="libxslt in /usr/pkg/" + ngx_feature_path="/usr/pkg/include/libxml2 /usr/pkg/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lxml2 -lxslt" + else + ngx_feature_libs="-L/usr/pkg/lib -lxml2 -lxslt" + fi + + . auto/feature +fi + + +if [ $ngx_found = no ]; then + + # MacPorts + + ngx_feature="libxslt in /opt/local/" + ngx_feature_path="/opt/local/include/libxml2 /opt/local/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lxml2 -lxslt" + else + ngx_feature_libs="-L/opt/local/lib -lxml2 -lxslt" + fi + + . auto/feature +fi + + +if [ $ngx_found = yes ]; then + CORE_INCS="$CORE_INCS $ngx_feature_path" + CORE_LIBS="$CORE_LIBS $ngx_feature_libs" +fi diff --git a/auto/modules b/auto/modules --- a/auto/modules +++ b/auto/modules @@ -76,6 +76,9 @@ fi # the module order is important +# ngx_http_static_module +# ngx_http_gzip_static_module +# ngx_http_dav_module # ngx_http_autoindex_module # ngx_http_index_module # @@ -92,6 +95,8 @@ fi # ngx_http_postpone_filter # ngx_http_charset_filter # ngx_http_ssi_filter +# ngx_http_xslt_filter +# ngx_http_sub_filter # ngx_http_addition_filter # ngx_http_userid_filter # ngx_http_headers_filter @@ -129,6 +134,12 @@ if [ $HTTP_SSI = YES ]; then HTTP_SRCS="$HTTP_SRCS $HTTP_SSI_SRCS" fi +if [ $HTTP_XSLT = YES ]; then + USE_LIBXSLT=YES + HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_XSLT_FILTER_MODULE" + HTTP_SRCS="$HTTP_SRCS $HTTP_XSLT_SRCS" +fi + if [ $HTTP_SUB = YES ]; then HTTP_FILTER_MODULES="$HTTP_FILTER_MODULES $HTTP_SUB_FILTER_MODULE" HTTP_SRCS="$HTTP_SRCS $HTTP_SUB_SRCS" diff --git a/auto/options b/auto/options --- a/auto/options +++ b/auto/options @@ -56,6 +56,7 @@ HTTP_SSL=NO HTTP_SSI=YES HTTP_POSTPONE=NO HTTP_REALIP=NO +HTTP_XSLT=NO HTTP_SUB=NO HTTP_ADDITION=NO HTTP_DAV=NO @@ -115,6 +116,8 @@ ZLIB_ASM=NO USE_PERL=NO NGX_PERL=perl +USE_LIBXSLT=NO + NGX_GOOGLE_PERFTOOLS=NO NGX_CPU_CACHE_LINE= @@ -162,6 +165,7 @@ do --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; + --with-http_xslt_module) HTTP_XSLT=YES ;; --with-http_sub_module) HTTP_SUB=YES ;; --with-http_dav_module) HTTP_DAV=YES ;; --with-http_flv_module) HTTP_FLV=YES ;; @@ -276,6 +280,7 @@ cat << END --with-http_ssl_module enable ngx_http_ssl_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module + --with-http_xslt_module enable ngx_http_xslt_module --with-http_sub_module enable ngx_http_sub_module --with-http_dav_module enable ngx_http_dav_module --with-http_flv_module enable ngx_http_flv_module diff --git a/auto/sources b/auto/sources --- a/auto/sources +++ b/auto/sources @@ -332,6 +332,10 @@ HTTP_SSI_DEPS=src/http/modules/ngx_http_ HTTP_SSI_SRCS=src/http/modules/ngx_http_ssi_filter_module.c +HTTP_XSLT_FILTER_MODULE=ngx_http_xslt_filter_module +HTTP_XSLT_SRCS=src/http/modules/ngx_http_xslt_filter_module.c + + HTTP_SUB_FILTER_MODULE=ngx_http_sub_filter_module HTTP_SUB_SRCS=src/http/modules/ngx_http_sub_filter_module.c diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c new file mode 100644 --- /dev/null +++ b/src/http/modules/ngx_http_xslt_filter_module.c @@ -0,0 +1,1109 @@ + +/* + * Copyright (C) Igor Sysoev + */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#ifndef NGX_HTTP_XSLT_REUSE_DTD +#define NGX_HTTP_XSLT_REUSE_DTD 1 +#endif + + +typedef struct { + ngx_array_t *lengths; + ngx_array_t *values; +} ngx_http_xslt_param_t; + + +typedef struct { + xsltStylesheetPtr stylesheet; + ngx_array_t params; /* ngx_http_xslt_param_t */ +} ngx_http_xslt_sheet_t; + + +typedef struct { + xmlDtdPtr dtd; + ngx_array_t sheets; /* ngx_http_xslt_sheet_t */ + ngx_hash_t types_hash; + ngx_array_t *keys; +} ngx_http_xslt_filter_conf_t; + + +typedef struct { + xmlDocPtr doc; + xmlParserCtxtPtr ctxt; + xmlSAXHandler *sax; + ngx_http_request_t *request; + ngx_array_t params; + unsigned done:1; + unsigned html:1; +} ngx_http_xslt_filter_ctx_t; + + +static ngx_int_t ngx_http_xslt_send(ngx_http_request_t *r, + ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b); +static ngx_int_t ngx_http_xslt_filter_internal_error(ngx_http_request_t *r); +static ngx_int_t ngx_http_xslt_add_chunk(ngx_http_request_t *r, + ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b); + + +static void ngx_http_xslt_sax_start_document(void *data); +static void ngx_http_xslt_sax_end_document(void *data); +static void ngx_http_xslt_sax_internal_subset(void *data, const xmlChar *name, + const xmlChar *externalId, const xmlChar *systemId); +static void ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name, + const xmlChar *externalId, const xmlChar *systemId); +static void ngx_http_xslt_sax_entity_decl(void *data, const xmlChar *name, + int type, const xmlChar *publicId, const xmlChar *systemId, + xmlChar *content); +static void ngx_http_xslt_sax_attribute_decl(void *data, const xmlChar *elem, + const xmlChar *fullname, int type, int def, const xmlChar *defaultValue, + xmlEnumerationPtr tree); +static void ngx_http_xslt_sax_element_decl(void *data, const xmlChar * name, + int type, xmlElementContentPtr content); +static void ngx_http_xslt_sax_notation_decl(void *data, const xmlChar *name, + const xmlChar *publicId, const xmlChar *systemId); +static void ngx_http_xslt_sax_unparsed_entity_decl(void *data, + const xmlChar *name, const xmlChar *publicId, const xmlChar *systemId, + const xmlChar *notationName); +static void ngx_http_xslt_sax_start_element(void *data, + const xmlChar *localname, const xmlChar *prefix, const xmlChar *URI, + int nb_namespaces, const xmlChar **namespaces, int nb_attributes, + int nb_defaulted, const xmlChar **attributes); +static void ngx_http_xslt_sax_end_element(void *data, + const xmlChar * localname ATTRIBUTE_UNUSED, + const xmlChar * prefix ATTRIBUTE_UNUSED, + const xmlChar * URI ATTRIBUTE_UNUSED); +static void ngx_http_xslt_sax_characters(void *data, const xmlChar *p, int len); +static void ngx_http_xslt_sax_cdata_block(void *data, const xmlChar *p, + int len); +static xmlEntityPtr ngx_http_xslt_sax_get_entity(void *data, + const xmlChar *name); +static xmlEntityPtr ngx_http_xslt_sax_get_parameter_entity(void *data, + const xmlChar *name); +static xmlParserInputPtr ngx_http_xslt_sax_resolve_entity(void *data, + const xmlChar *publicId, const xmlChar *systemId); +static void ngx_http_xslt_sax_reference(void *data, const xmlChar *name); +static void ngx_http_xslt_sax_comment(void *data, const xmlChar *value); +static void ngx_http_xslt_sax_processing_instruction(void *data, + const xmlChar *target, const xmlChar *pidata); +static int ngx_http_xslt_sax_is_standalone(void *data); +static int ngx_http_xslt_sax_has_internal_subset(void *data); +static int ngx_http_xslt_sax_has_external_subset(void *data); +static void ngx_cdecl ngx_http_xslt_sax_error(void *data, const char *msg, ...); + + +static ngx_buf_t *ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r, + ngx_http_xslt_filter_ctx_t *ctx); +static ngx_int_t ngx_http_xslt_params(ngx_http_request_t *r, + ngx_http_xslt_filter_ctx_t *ctx, ngx_array_t *params); +static void ngx_http_xslt_cleanup(void *data); + +static char *ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static char *ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static void ngx_http_xslt_cleanup_stylesheet(void *data); +static void *ngx_http_xslt_filter_create_conf(ngx_conf_t *cf); +static char *ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, + void *child); +static ngx_int_t ngx_http_xslt_filter_init(ngx_conf_t *cf); + + +ngx_str_t ngx_http_xslt_default_types[] = { + ngx_string("text/xml"), + ngx_null_string +}; + + +static ngx_command_t ngx_http_xslt_filter_commands[] = { + + { ngx_string("xml_entities"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, + ngx_http_xslt_entities, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("xslt_stylesheet"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_1MORE, + ngx_http_xslt_stylesheet, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("xslt_types"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_1MORE, + ngx_http_types_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_xslt_filter_conf_t, keys), + &ngx_http_xslt_default_types[0] }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_xslt_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_xslt_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_xslt_filter_create_conf, /* create location configuration */ + ngx_http_xslt_filter_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_xslt_filter_module = { + NGX_MODULE_V1, + &ngx_http_xslt_filter_module_ctx, /* module context */ + ngx_http_xslt_filter_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static ngx_int_t +ngx_http_xslt_header_filter(ngx_http_request_t *r) +{ + ngx_http_xslt_filter_ctx_t *ctx; + ngx_http_xslt_filter_conf_t *conf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter header"); + + if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { + return ngx_http_next_header_filter(r); + } + + conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module); + + if (conf->sheets.nelts == 0 + || ngx_http_test_content_type(r, &conf->types_hash) == NULL) + { + return ngx_http_next_header_filter(r); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module); + + if (ctx) { + return ngx_http_next_header_filter(r); + } + + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xslt_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_xslt_filter_module); + + r->main_filter_need_in_memory = 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_xslt_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + ngx_chain_t *cl; + ngx_http_xslt_filter_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter body"); + + if (in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module); + + if (ctx == NULL || ctx->done) { + return ngx_http_next_body_filter(r, in); + } + + for (cl = in; cl; cl = cl->next) { + + if (ngx_http_xslt_add_chunk(r, ctx, cl->buf) != NGX_OK) { + + if (ctx->ctxt->myDoc){ + +#if (NGX_HTTP_XSLT_REUSE_DTD) + ctx->ctxt->myDoc->extSubset = NULL; +#endif + xmlFreeDoc(ctx->ctxt->myDoc); + } + + xmlFreeParserCtxt(ctx->ctxt); + + return ngx_http_xslt_send(r, ctx, NULL); + } + + if (cl->buf->last_buf) { + + ctx->doc = ctx->ctxt->myDoc; + +#if (NGX_HTTP_XSLT_REUSE_DTD) + ctx->doc->extSubset = NULL; +#endif + + xmlFreeParserCtxt(ctx->ctxt); + + if (ctx->ctxt->wellFormed) { + return ngx_http_xslt_send(r, ctx, + ngx_http_xslt_apply_stylesheet(r, ctx)); + } + + xmlFreeDoc(ctx->doc); + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "not well formed XML document"); + + return ngx_http_xslt_send(r, ctx, NULL); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_xslt_send(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx, + ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_chain_t out; + ngx_pool_cleanup_t *cln; + + ctx->done = 1; + + if (b == NULL) { + return ngx_http_xslt_filter_internal_error(r); + } + + cln = ngx_pool_cleanup_add(r->pool, 0); + + if (cln == NULL) { + ngx_free(b->pos); + return ngx_http_special_response_handler(r, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + if (ctx->html) { + r->headers_out.content_type_len = sizeof("text/html") - 1; + r->headers_out.content_type.len = sizeof("text/html") - 1; + r->headers_out.content_type.data = (u_char *) "text/html"; + } + + r->headers_out.content_length_n = b->last - b->pos; + + if (r->headers_out.content_length) { + r->headers_out.content_length->hash = 0; + r->headers_out.content_length = NULL; + } + + r->allow_ranges = 1; + + rc = ngx_http_next_header_filter(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + ngx_free(b->pos); + return rc; + } + + cln->handler = ngx_http_xslt_cleanup; + cln->data = b->pos; + + out.buf = b; + out.next = NULL; + + return ngx_http_next_body_filter(r, &out); +} + + +static ngx_int_t +ngx_http_xslt_filter_internal_error(ngx_http_request_t *r) +{ + ngx_int_t rc; + + /* clear the modules contexts */ + ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module); + + rc = ngx_http_special_response_handler(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + + /* NGX_ERROR resets any pending data */ + + return (rc == NGX_OK) ? NGX_ERROR : rc; +} + + +static ngx_int_t +ngx_http_xslt_add_chunk(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx, + ngx_buf_t *b) +{ + int err; + xmlSAXHandler *sax; + xmlParserCtxtPtr ctxt; + + if (ctx->ctxt == NULL) { + + ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL); + if (ctxt == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xmlCreatePushParserCtxt() failed"); + return NGX_ERROR; + } + + ctx->sax = ngx_palloc(r->pool, sizeof(xmlSAXHandler)); + if (ctx->sax == NULL) { + return NGX_ERROR; + } + + sax = ctxt->sax; + + ngx_memcpy(ctx->sax, sax, sizeof(xmlSAXHandler)); + + sax->startDocument = ngx_http_xslt_sax_start_document; + sax->endDocument = ngx_http_xslt_sax_end_document; + + sax->internalSubset = ngx_http_xslt_sax_internal_subset; + sax->externalSubset = ngx_http_xslt_sax_external_subset; + sax->entityDecl = ngx_http_xslt_sax_entity_decl; + sax->attributeDecl = ngx_http_xslt_sax_attribute_decl; + sax->elementDecl = ngx_http_xslt_sax_element_decl; + sax->notationDecl = ngx_http_xslt_sax_notation_decl; + sax->unparsedEntityDecl = ngx_http_xslt_sax_unparsed_entity_decl; + sax->setDocumentLocator = NULL; + + sax->startElementNs = ngx_http_xslt_sax_start_element; + sax->endElementNs = ngx_http_xslt_sax_end_element; + + sax->characters = ngx_http_xslt_sax_characters; + sax->ignorableWhitespace = ngx_http_xslt_sax_characters; + sax->cdataBlock = ngx_http_xslt_sax_cdata_block; + sax->getEntity = ngx_http_xslt_sax_get_entity; + sax->resolveEntity = ngx_http_xslt_sax_resolve_entity; + sax->getParameterEntity = ngx_http_xslt_sax_get_parameter_entity; + sax->reference = ngx_http_xslt_sax_reference; + sax->comment = ngx_http_xslt_sax_comment; + sax->processingInstruction = ngx_http_xslt_sax_processing_instruction; + + sax->isStandalone = ngx_http_xslt_sax_is_standalone; + sax->hasInternalSubset = ngx_http_xslt_sax_has_internal_subset; + sax->hasExternalSubset = ngx_http_xslt_sax_has_external_subset; + + sax->warning = NULL; + sax->error = ngx_http_xslt_sax_error; + sax->fatalError = ngx_http_xslt_sax_error; + + ctxt->userData = ctx; + + ctxt->replaceEntities = 1; + ctxt->loadsubset = 1; + + ctx->ctxt = ctxt; + ctx->request = r; + } + + err = xmlParseChunk(ctx->ctxt, (char *) b->pos, + (int) (b->last - b->pos), b->last_buf); + + if (err == 0) { + b->pos = b->last; + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xmlParseChunk() failed, error:%d", err); + + return NGX_ERROR; +} + + +static void +ngx_http_xslt_sax_start_document(void *data) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->startDocument(ctx->ctxt); +} + + +static void +ngx_http_xslt_sax_end_document(void *data) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->endDocument(ctx->ctxt); +} + + +static void +ngx_http_xslt_sax_internal_subset(void *data, const xmlChar *name, + const xmlChar *externalId, const xmlChar *systemId) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->internalSubset(ctx->ctxt, name, externalId, systemId); +} + + +static void +ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name, + const xmlChar *externalId, const xmlChar *systemId) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + xmlDocPtr doc; + xmlDtdPtr dtd; + ngx_http_request_t *r; + ngx_http_xslt_filter_conf_t *conf; + + r = ctx->request; + + conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter extSubset: \"%s\" \"%s\" \"%s\"", + name ? name : (xmlChar *) "", + externalId ? externalId : (xmlChar *) "", + systemId ? systemId : (xmlChar *) ""); + + doc = ctx->ctxt->myDoc; + +#if (NGX_HTTP_XSLT_REUSE_DTD) + + dtd = conf->dtd; + +#else + + dtd = xmlCopyDtd(conf->dtd); + if (dtd == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xmlCopyDtd() failed"); + return; + } + + dtd->name = xmlStrdup(name); + + if (doc->children == NULL) { + xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd); + + } else { + xmlAddPrevSibling(doc->children, (xmlNodePtr) dtd); + } + +#endif + + doc->extSubset = dtd; +} + + +static void +ngx_http_xslt_sax_entity_decl(void *data, const xmlChar *name, int type, + const xmlChar *publicId, const xmlChar *systemId, xmlChar *content) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->entityDecl(ctx->ctxt, name, type, publicId, systemId, content); +} + + +static void +ngx_http_xslt_sax_attribute_decl(void *data, const xmlChar *elem, + const xmlChar *fullname, int type, int def, const xmlChar *defaultValue, + xmlEnumerationPtr tree) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->attributeDecl(ctx->ctxt, elem, fullname, type, def, defaultValue, + tree); +} + + +static void +ngx_http_xslt_sax_element_decl(void *data, const xmlChar * name, int type, + xmlElementContentPtr content) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->elementDecl(ctx->ctxt, name, type, content); +} + + +static void +ngx_http_xslt_sax_notation_decl(void *data, const xmlChar *name, + const xmlChar *publicId, const xmlChar *systemId) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->notationDecl(ctx->ctxt, name, publicId, systemId); +} + + +static void +ngx_http_xslt_sax_unparsed_entity_decl(void *data, const xmlChar *name, + const xmlChar *publicId, const xmlChar *systemId, + const xmlChar *notationName) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->unparsedEntityDecl(ctx->ctxt, name, publicId, systemId, + notationName); +} + + +static void +ngx_http_xslt_sax_start_element(void *data, const xmlChar *localname, + const xmlChar *prefix, const xmlChar *URI, int nb_namespaces, + const xmlChar **namespaces, int nb_attributes, int nb_defaulted, + const xmlChar **attributes) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->startElementNs(ctx->ctxt, localname, prefix, URI, nb_namespaces, + namespaces, nb_attributes, nb_defaulted, attributes); +} + + +static void +ngx_http_xslt_sax_end_element(void *data, + const xmlChar * localname ATTRIBUTE_UNUSED, + const xmlChar * prefix ATTRIBUTE_UNUSED, + const xmlChar * URI ATTRIBUTE_UNUSED) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->endElementNs(ctx->ctxt, localname, prefix, URI); +} + + +static void +ngx_http_xslt_sax_characters(void *data, const xmlChar *p, int len) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->characters(ctx->ctxt, p, len); +} + + +static void +ngx_http_xslt_sax_cdata_block(void *data, const xmlChar *p, int len) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->cdataBlock(ctx->ctxt, p, len); +} + + +static xmlEntityPtr +ngx_http_xslt_sax_get_entity(void *data, const xmlChar *name) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + return ctx->sax->getEntity(ctx->ctxt, name); +} + + +static xmlEntityPtr +ngx_http_xslt_sax_get_parameter_entity(void *data, const xmlChar *name) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + return ctx->sax->getParameterEntity(ctx->ctxt, name); +} + + +static xmlParserInputPtr +ngx_http_xslt_sax_resolve_entity(void *data, const xmlChar *publicId, + const xmlChar *systemId) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + return ctx->sax->resolveEntity(ctx->ctxt, publicId, systemId); +} + + +static void +ngx_http_xslt_sax_reference(void *data, const xmlChar *name) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->reference(ctx->ctxt, name); +} + + +static void +ngx_http_xslt_sax_comment(void *data, const xmlChar *value) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->comment(ctx->ctxt, value); +} + + +static void +ngx_http_xslt_sax_processing_instruction(void *data, const xmlChar *target, + const xmlChar *pidata) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + ctx->sax->processingInstruction(ctx->ctxt, target, pidata); +} + + +static int +ngx_http_xslt_sax_is_standalone(void *data) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + return ctx->sax->isStandalone(ctx->ctxt); +} + + +static int +ngx_http_xslt_sax_has_internal_subset(void *data) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + return ctx->sax->hasInternalSubset(ctx->ctxt); +} + + +static int +ngx_http_xslt_sax_has_external_subset(void *data) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + return ctx->sax->hasExternalSubset(ctx->ctxt); +} + + +static void ngx_cdecl +ngx_http_xslt_sax_error(void *data, const char *msg, ...) +{ + ngx_http_xslt_filter_ctx_t *ctx = data; + + size_t n; + va_list args; + u_char buf[NGX_MAX_ERROR_STR]; + + buf[0] = '\0'; + + va_start(args, msg); + n = (size_t) vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args); + va_end(args); + + while (--n && (buf[n] == CR || buf[n] == LF)) { /* void */ } + + ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, + "libxml2 error: \"%*s\"", n, buf); +} + + +static ngx_buf_t * +ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r, + ngx_http_xslt_filter_ctx_t *ctx) +{ + int len, rc; + ngx_buf_t *b; + ngx_uint_t i; + xmlChar *buf; + xmlDocPtr doc, res; + ngx_http_xslt_sheet_t *sheet; + ngx_http_xslt_filter_conf_t *conf; + + conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module); + sheet = conf->sheets.elts; + doc = ctx->doc; + + /* preallocate array for 4 params */ + + if (ngx_array_init(&ctx->params, r->pool, 4 * 2 + 1, sizeof(char *)) + != NGX_OK) + { + xmlFreeDoc(doc); + return NULL; + } + + for (i = 0; i < conf->sheets.nelts; i++) { + + if (ngx_http_xslt_params(r, ctx, &sheet[i].params) != NGX_OK) { + xmlFreeDoc(doc); + return NULL; + } + + res = xsltApplyStylesheet(sheet[i].stylesheet, doc, ctx->params.elts); + + xmlFreeDoc(doc); + + if (res == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xsltApplyStylesheet() failed"); + return NULL; + } + + doc = res; + + /* reset array elements */ + ctx->params.nelts = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter doc type: %d", doc->type); + + ctx->html = (doc->type == XML_HTML_DOCUMENT_NODE) ? 1 : 0; + + rc = xsltSaveResultToString(&buf, &len, doc, sheet[i - 1].stylesheet); + + xmlFreeDoc(doc); + + if (rc != 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xsltSaveResultToString() failed"); + return NULL; + } + + if (len == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "xsltSaveResultToString() returned zero-length result"); + return NULL; + } + + b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + ngx_free(buf); + return NULL; + } + + b->pos = buf; + b->last = buf + len; + b->memory = 1; + b->last_buf = 1; + + return b; +} + + +static ngx_int_t +ngx_http_xslt_params(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx, + ngx_array_t *params) +{ + u_char *p, *last, *value, *dst, *src, **s; + size_t len; + ngx_uint_t i; + ngx_str_t string; + ngx_http_xslt_param_t *param; + + param = params->elts; + + for (i = 0; i < params->nelts; i++) { + + if (ngx_http_script_run(r, &string, param[i].lengths->elts, 1, + param[i].values->elts) + == NULL) + { + return NGX_ERROR; + } + + last = string.data + string.len - 1; + *last = '\0'; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter param: \"%s\"", string.data); + + p = string.data; + + while (p && *p) { + + value = p; + p = (u_char *) ngx_strchr(p, '='); + if (p == NULL) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "invalid libxslt parameter \"%s\"", value); + return NGX_ERROR; + } + *p++ = '\0'; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter param name: \"%s\"", value); + + s = ngx_array_push(&ctx->params); + if (s == NULL) { + return NGX_ERROR; + } + + *s = value; + + value = p; + p = (u_char *) ngx_strchr(p, ':'); + + if (p) { + len = p - value; + *p++ = '\0'; + + } else { + len = last - value; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter param value: \"%s\"", value); + + dst = value; + src = value; + + ngx_unescape_uri(&dst, &src, len, 0); + + *dst = '\0'; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "xslt filter param unescaped: \"%s\"", value); + + s = ngx_array_push(&ctx->params); + if (s == NULL) { + return NGX_ERROR; + } + + *s = value; + } + } + + s = ngx_array_push(&ctx->params); + if (s == NULL) { + return NGX_ERROR; + } + + *s = NULL; + + return NGX_OK; +} + + +static void +ngx_http_xslt_cleanup(void *data) +{ + ngx_free(data); +} + + +static char * +ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_xslt_filter_conf_t *xlcf = conf; + + ngx_str_t *value; + + if (xlcf->dtd) { + return "is duplicate"; + } + + value = cf->args->elts; + + xlcf->dtd = xmlParseDTD(NULL, (xmlChar *) value[1].data); + + if (xlcf->dtd == NULL) { + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "xmlParseDTD() failed"); + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + + +static char * +ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_xslt_filter_conf_t *xlcf = conf; + + ngx_str_t *value; + ngx_uint_t i, n; + ngx_pool_cleanup_t *cln; + ngx_http_xslt_sheet_t *sheet; + ngx_http_xslt_param_t *param; + ngx_http_script_compile_t sc; + + value = cf->args->elts; + + if (xlcf->sheets.elts == NULL) { + if (ngx_array_init(&xlcf->sheets, cf->pool, 1, + sizeof(ngx_http_xslt_sheet_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + + sheet = ngx_array_push(&xlcf->sheets); + if (sheet == NULL) { + return NGX_CONF_ERROR; + } + + ngx_memzero(sheet, sizeof(ngx_http_xslt_sheet_t)); + + if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) { + return NGX_CONF_ERROR; + } + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NGX_CONF_ERROR; + } + + sheet->stylesheet = xsltParseStylesheetFile(value[1].data); + if (sheet->stylesheet == NULL) { + ngx_conf_log_error(NGX_LOG_ERR, cf, 0, + "xsltParseStylesheetFile(\"%s\") failed", + value[1].data); + return NGX_CONF_ERROR; + } + + cln->handler = ngx_http_xslt_cleanup_stylesheet; + cln->data = sheet->stylesheet; + + n = cf->args->nelts; + + if (n == 2) { + return NGX_CONF_OK; + } + + if (ngx_array_init(&sheet->params, cf->pool, n - 2, + sizeof(ngx_http_xslt_param_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + for (i = 2; i < n; i++) { + + param = ngx_array_push(&sheet->params); + if (param == NULL) { + return NGX_CONF_ERROR; + } + + param->lengths = NULL; + param->values = NULL; + + ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); + + sc.cf = cf; + sc.source = &value[i]; + sc.lengths = ¶m->lengths; + sc.values = ¶m->values; + sc.variables = ngx_http_script_variables_count(&value[i]); + sc.complete_lengths = 1; + sc.complete_values = 1; + + if (ngx_http_script_compile(&sc) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + + return NGX_CONF_OK; +} + + +static void +ngx_http_xslt_cleanup_stylesheet(void *data) +{ + xsltStylesheetPtr stylesheet = data; + + xsltFreeStylesheet(stylesheet); +} + + + +static void * +ngx_http_xslt_filter_create_conf(ngx_conf_t *cf) +{ + ngx_http_xslt_filter_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xslt_filter_conf_t)); + if (conf == NULL) { + return NGX_CONF_ERROR; + } + + /* + * set by ngx_pcalloc(): + * + * conf->dtd + * conf->sheets + */ + + return conf; +} + + +static char * +ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_xslt_filter_conf_t *prev = parent; + ngx_http_xslt_filter_conf_t *conf = child; + + if (conf->dtd == NULL) { + conf->dtd = prev->dtd; + } + + if (conf->sheets.nelts == 0) { + conf->sheets = prev->sheets; + } + + if (ngx_http_merge_types(cf, conf->keys, &conf->types_hash, prev->keys, + &prev->types_hash, ngx_http_xslt_default_types) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static ngx_int_t +ngx_http_xslt_filter_init(ngx_conf_t *cf) +{ + xmlInitParser(); + + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_xslt_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_xslt_body_filter; + + return NGX_OK; +} + + +static void +ngx_http_xslt_filter_exit(ngx_cycle_t *cycle) +{ + xsltCleanupGlobals(); + xmlCleanupParser(); +}