diff src/http/modules/ngx_http_xslt_filter_module.c @ 392:34fb3a573548 NGINX_0_7_8

nginx 0.7.8 *) Feature: the ngx_http_xslt_module. *) Feature: the "$arg_..." variables. *) Feature: Solaris directio support. Thanks to Ivan Debnar. *) Bugfix: now if FastCGI server sends a "Location" header line without status line, then nginx uses 302 status code. Thanks to Maxim Dounin.
author Igor Sysoev <http://sysoev.ru>
date Mon, 04 Aug 2008 00:00:00 +0400
parents
children 05981f639d21
line wrap: on
line diff
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 <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxslt/xslt.h>
+#include <libxslt/xsltInternals.h>
+#include <libxslt/transform.h>
+#include <libxslt/xsltutils.h>
+
+
+#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 = &param->lengths;
+        sc.values = &param->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();
+}