ngx_http_xslt_filter_module.c revision e18a033b
1
2/*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7
8#include <ngx_config.h>
9#include <ngx_core.h>
10#include <ngx_http.h>
11
12#include <libxml/parser.h>
13#include <libxml/tree.h>
14#include <libxslt/xslt.h>
15#include <libxslt/xsltInternals.h>
16#include <libxslt/transform.h>
17#include <libxslt/variables.h>
18#include <libxslt/xsltutils.h>
19
20#if (NGX_HAVE_EXSLT)
21#include <libexslt/exslt.h>
22#endif
23
24
25#ifndef NGX_HTTP_XSLT_REUSE_DTD
26#define NGX_HTTP_XSLT_REUSE_DTD  1
27#endif
28
29
30typedef struct {
31    u_char                    *name;
32    void                      *data;
33} ngx_http_xslt_file_t;
34
35
36typedef struct {
37    ngx_array_t                dtd_files;    /* ngx_http_xslt_file_t */
38    ngx_array_t                sheet_files;  /* ngx_http_xslt_file_t */
39} ngx_http_xslt_filter_main_conf_t;
40
41
42typedef struct {
43    u_char                    *name;
44    ngx_http_complex_value_t   value;
45    ngx_uint_t                 quote;        /* unsigned  quote:1; */
46} ngx_http_xslt_param_t;
47
48
49typedef struct {
50    xsltStylesheetPtr          stylesheet;
51    ngx_array_t                params;       /* ngx_http_xslt_param_t */
52} ngx_http_xslt_sheet_t;
53
54
55typedef struct {
56    xmlDtdPtr                  dtd;
57    ngx_array_t                sheets;       /* ngx_http_xslt_sheet_t */
58    ngx_hash_t                 types;
59    ngx_array_t               *types_keys;
60    ngx_array_t               *params;       /* ngx_http_xslt_param_t */
61    ngx_flag_t                 last_modified;
62} ngx_http_xslt_filter_loc_conf_t;
63
64
65typedef struct {
66    xmlDocPtr                  doc;
67    xmlParserCtxtPtr           ctxt;
68    xsltTransformContextPtr    transform;
69    ngx_http_request_t        *request;
70    ngx_array_t                params;
71
72    ngx_uint_t                 done;         /* unsigned  done:1; */
73} ngx_http_xslt_filter_ctx_t;
74
75
76static ngx_int_t ngx_http_xslt_send(ngx_http_request_t *r,
77    ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
78static ngx_int_t ngx_http_xslt_add_chunk(ngx_http_request_t *r,
79    ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
80
81
82static void ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
83    const xmlChar *externalId, const xmlChar *systemId);
84static void ngx_cdecl ngx_http_xslt_sax_error(void *data, const char *msg, ...);
85
86
87static ngx_buf_t *ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
88    ngx_http_xslt_filter_ctx_t *ctx);
89static ngx_int_t ngx_http_xslt_params(ngx_http_request_t *r,
90    ngx_http_xslt_filter_ctx_t *ctx, ngx_array_t *params, ngx_uint_t final);
91static u_char *ngx_http_xslt_content_type(xsltStylesheetPtr s);
92static u_char *ngx_http_xslt_encoding(xsltStylesheetPtr s);
93static void ngx_http_xslt_cleanup(void *data);
94
95static char *ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd,
96    void *conf);
97static char *ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd,
98    void *conf);
99static char *ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd,
100    void *conf);
101static void ngx_http_xslt_cleanup_dtd(void *data);
102static void ngx_http_xslt_cleanup_stylesheet(void *data);
103static void *ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf);
104static void *ngx_http_xslt_filter_create_conf(ngx_conf_t *cf);
105static char *ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent,
106    void *child);
107static ngx_int_t ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf);
108static ngx_int_t ngx_http_xslt_filter_init(ngx_conf_t *cf);
109static void ngx_http_xslt_filter_exit(ngx_cycle_t *cycle);
110
111
112static ngx_str_t  ngx_http_xslt_default_types[] = {
113    ngx_string("text/xml"),
114    ngx_null_string
115};
116
117
118static ngx_command_t  ngx_http_xslt_filter_commands[] = {
119
120    { ngx_string("xml_entities"),
121      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
122      ngx_http_xslt_entities,
123      NGX_HTTP_LOC_CONF_OFFSET,
124      0,
125      NULL },
126
127    { ngx_string("xslt_stylesheet"),
128      NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
129      ngx_http_xslt_stylesheet,
130      NGX_HTTP_LOC_CONF_OFFSET,
131      0,
132      NULL },
133
134    { ngx_string("xslt_param"),
135      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
136      ngx_http_xslt_param,
137      NGX_HTTP_LOC_CONF_OFFSET,
138      0,
139      NULL },
140
141    { ngx_string("xslt_string_param"),
142      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
143      ngx_http_xslt_param,
144      NGX_HTTP_LOC_CONF_OFFSET,
145      0,
146      (void *) 1 },
147
148    { ngx_string("xslt_types"),
149      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
150      ngx_http_types_slot,
151      NGX_HTTP_LOC_CONF_OFFSET,
152      offsetof(ngx_http_xslt_filter_loc_conf_t, types_keys),
153      &ngx_http_xslt_default_types[0] },
154
155    { ngx_string("xslt_last_modified"),
156      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
157      ngx_conf_set_flag_slot,
158      NGX_HTTP_LOC_CONF_OFFSET,
159      offsetof(ngx_http_xslt_filter_loc_conf_t, last_modified),
160      NULL },
161
162      ngx_null_command
163};
164
165
166static ngx_http_module_t  ngx_http_xslt_filter_module_ctx = {
167    ngx_http_xslt_filter_preconfiguration, /* preconfiguration */
168    ngx_http_xslt_filter_init,             /* postconfiguration */
169
170    ngx_http_xslt_filter_create_main_conf, /* create main configuration */
171    NULL,                                  /* init main configuration */
172
173    NULL,                                  /* create server configuration */
174    NULL,                                  /* merge server configuration */
175
176    ngx_http_xslt_filter_create_conf,      /* create location configuration */
177    ngx_http_xslt_filter_merge_conf        /* merge location configuration */
178};
179
180
181ngx_module_t  ngx_http_xslt_filter_module = {
182    NGX_MODULE_V1,
183    &ngx_http_xslt_filter_module_ctx,      /* module context */
184    ngx_http_xslt_filter_commands,         /* module directives */
185    NGX_HTTP_MODULE,                       /* module type */
186    NULL,                                  /* init master */
187    NULL,                                  /* init module */
188    NULL,                                  /* init process */
189    NULL,                                  /* init thread */
190    NULL,                                  /* exit thread */
191    ngx_http_xslt_filter_exit,             /* exit process */
192    ngx_http_xslt_filter_exit,             /* exit master */
193    NGX_MODULE_V1_PADDING
194};
195
196
197static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
198static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
199
200
201static ngx_int_t
202ngx_http_xslt_header_filter(ngx_http_request_t *r)
203{
204    ngx_http_xslt_filter_ctx_t       *ctx;
205    ngx_http_xslt_filter_loc_conf_t  *conf;
206
207    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
208                   "xslt filter header");
209
210    if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) {
211        return ngx_http_next_header_filter(r);
212    }
213
214    conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
215
216    if (conf->sheets.nelts == 0
217        || ngx_http_test_content_type(r, &conf->types) == NULL)
218    {
219        return ngx_http_next_header_filter(r);
220    }
221
222    ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
223
224    if (ctx) {
225        return ngx_http_next_header_filter(r);
226    }
227
228    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_xslt_filter_ctx_t));
229    if (ctx == NULL) {
230        return NGX_ERROR;
231    }
232
233    ngx_http_set_ctx(r, ctx, ngx_http_xslt_filter_module);
234
235    r->main_filter_need_in_memory = 1;
236
237    return NGX_OK;
238}
239
240
241static ngx_int_t
242ngx_http_xslt_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
243{
244    int                          wellFormed;
245    ngx_chain_t                 *cl;
246    ngx_http_xslt_filter_ctx_t  *ctx;
247
248    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
249                   "xslt filter body");
250
251    if (in == NULL) {
252        return ngx_http_next_body_filter(r, in);
253    }
254
255    ctx = ngx_http_get_module_ctx(r, ngx_http_xslt_filter_module);
256
257    if (ctx == NULL || ctx->done) {
258        return ngx_http_next_body_filter(r, in);
259    }
260
261    for (cl = in; cl; cl = cl->next) {
262
263        if (ngx_http_xslt_add_chunk(r, ctx, cl->buf) != NGX_OK) {
264
265            if (ctx->ctxt->myDoc) {
266
267#if (NGX_HTTP_XSLT_REUSE_DTD)
268                ctx->ctxt->myDoc->extSubset = NULL;
269#endif
270                xmlFreeDoc(ctx->ctxt->myDoc);
271            }
272
273            xmlFreeParserCtxt(ctx->ctxt);
274
275            return ngx_http_xslt_send(r, ctx, NULL);
276        }
277
278        if (cl->buf->last_buf || cl->buf->last_in_chain) {
279
280            ctx->doc = ctx->ctxt->myDoc;
281
282#if (NGX_HTTP_XSLT_REUSE_DTD)
283            ctx->doc->extSubset = NULL;
284#endif
285
286            wellFormed = ctx->ctxt->wellFormed;
287
288            xmlFreeParserCtxt(ctx->ctxt);
289
290            if (wellFormed) {
291                return ngx_http_xslt_send(r, ctx,
292                                       ngx_http_xslt_apply_stylesheet(r, ctx));
293            }
294
295            xmlFreeDoc(ctx->doc);
296
297            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
298                          "not well formed XML document");
299
300            return ngx_http_xslt_send(r, ctx, NULL);
301        }
302    }
303
304    return NGX_OK;
305}
306
307
308static ngx_int_t
309ngx_http_xslt_send(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
310    ngx_buf_t *b)
311{
312    ngx_int_t                         rc;
313    ngx_chain_t                       out;
314    ngx_pool_cleanup_t               *cln;
315    ngx_http_xslt_filter_loc_conf_t  *conf;
316
317    ctx->done = 1;
318
319    if (b == NULL) {
320        return ngx_http_filter_finalize_request(r, &ngx_http_xslt_filter_module,
321                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
322    }
323
324    cln = ngx_pool_cleanup_add(r->pool, 0);
325
326    if (cln == NULL) {
327        ngx_free(b->pos);
328        return ngx_http_filter_finalize_request(r, &ngx_http_xslt_filter_module,
329                                               NGX_HTTP_INTERNAL_SERVER_ERROR);
330    }
331
332    if (r == r->main) {
333        r->headers_out.content_length_n = b->last - b->pos;
334
335        if (r->headers_out.content_length) {
336            r->headers_out.content_length->hash = 0;
337            r->headers_out.content_length = NULL;
338        }
339
340        conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
341
342        if (!conf->last_modified) {
343            ngx_http_clear_last_modified(r);
344            ngx_http_clear_etag(r);
345
346        } else {
347            ngx_http_weak_etag(r);
348        }
349    }
350
351    rc = ngx_http_next_header_filter(r);
352
353    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
354        ngx_free(b->pos);
355        return rc;
356    }
357
358    cln->handler = ngx_http_xslt_cleanup;
359    cln->data = b->pos;
360
361    out.buf = b;
362    out.next = NULL;
363
364    return ngx_http_next_body_filter(r, &out);
365}
366
367
368static ngx_int_t
369ngx_http_xslt_add_chunk(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
370    ngx_buf_t *b)
371{
372    int               err;
373    xmlParserCtxtPtr  ctxt;
374
375    if (ctx->ctxt == NULL) {
376
377        ctxt = xmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL);
378        if (ctxt == NULL) {
379            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
380                          "xmlCreatePushParserCtxt() failed");
381            return NGX_ERROR;
382        }
383        xmlCtxtUseOptions(ctxt, XML_PARSE_NOENT|XML_PARSE_DTDLOAD
384                                               |XML_PARSE_NOWARNING);
385
386        ctxt->sax->externalSubset = ngx_http_xslt_sax_external_subset;
387        ctxt->sax->setDocumentLocator = NULL;
388        ctxt->sax->error = ngx_http_xslt_sax_error;
389        ctxt->sax->fatalError = ngx_http_xslt_sax_error;
390        ctxt->sax->_private = ctx;
391
392        ctx->ctxt = ctxt;
393        ctx->request = r;
394    }
395
396    err = xmlParseChunk(ctx->ctxt, (char *) b->pos, (int) (b->last - b->pos),
397                        (b->last_buf) || (b->last_in_chain));
398
399    if (err == 0) {
400        b->pos = b->last;
401        return NGX_OK;
402    }
403
404    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
405                  "xmlParseChunk() failed, error:%d", err);
406
407    return NGX_ERROR;
408}
409
410
411static void
412ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
413    const xmlChar *externalId, const xmlChar *systemId)
414{
415    xmlParserCtxtPtr ctxt = data;
416
417    xmlDocPtr                         doc;
418    xmlDtdPtr                         dtd;
419    ngx_http_request_t               *r;
420    ngx_http_xslt_filter_ctx_t       *ctx;
421    ngx_http_xslt_filter_loc_conf_t  *conf;
422
423    ctx = ctxt->sax->_private;
424    r = ctx->request;
425
426    conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
427
428    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
429                   "xslt filter extSubset: \"%s\" \"%s\" \"%s\"",
430                   name ? name : (xmlChar *) "",
431                   externalId ? externalId : (xmlChar *) "",
432                   systemId ? systemId : (xmlChar *) "");
433
434    doc = ctxt->myDoc;
435
436#if (NGX_HTTP_XSLT_REUSE_DTD)
437
438    dtd = conf->dtd;
439
440#else
441
442    dtd = xmlCopyDtd(conf->dtd);
443    if (dtd == NULL) {
444        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
445                      "xmlCopyDtd() failed");
446        return;
447    }
448
449    if (doc->children == NULL) {
450        xmlAddChild((xmlNodePtr) doc, (xmlNodePtr) dtd);
451
452    } else {
453        xmlAddPrevSibling(doc->children, (xmlNodePtr) dtd);
454    }
455
456#endif
457
458    doc->extSubset = dtd;
459}
460
461
462static void ngx_cdecl
463ngx_http_xslt_sax_error(void *data, const char *msg, ...)
464{
465    xmlParserCtxtPtr ctxt = data;
466
467    size_t                       n;
468    va_list                      args;
469    ngx_http_xslt_filter_ctx_t  *ctx;
470    u_char                       buf[NGX_MAX_ERROR_STR];
471
472    ctx = ctxt->sax->_private;
473
474    buf[0] = '\0';
475
476    va_start(args, msg);
477    n = (size_t) vsnprintf((char *) buf, NGX_MAX_ERROR_STR, msg, args);
478    va_end(args);
479
480    while (--n && (buf[n] == CR || buf[n] == LF)) { /* void */ }
481
482    ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0,
483                  "libxml2 error: \"%*s\"", n + 1, buf);
484}
485
486
487static ngx_buf_t *
488ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
489    ngx_http_xslt_filter_ctx_t *ctx)
490{
491    int                               len, rc, doc_type;
492    u_char                           *type, *encoding;
493    ngx_buf_t                        *b;
494    ngx_uint_t                        i;
495    xmlChar                          *buf;
496    xmlDocPtr                         doc, res;
497    ngx_http_xslt_sheet_t            *sheet;
498    ngx_http_xslt_filter_loc_conf_t  *conf;
499
500    conf = ngx_http_get_module_loc_conf(r, ngx_http_xslt_filter_module);
501    sheet = conf->sheets.elts;
502    doc = ctx->doc;
503
504    /* preallocate array for 4 params */
505
506    if (ngx_array_init(&ctx->params, r->pool, 4 * 2 + 1, sizeof(char *))
507        != NGX_OK)
508    {
509        xmlFreeDoc(doc);
510        return NULL;
511    }
512
513    for (i = 0; i < conf->sheets.nelts; i++) {
514
515        ctx->transform = xsltNewTransformContext(sheet[i].stylesheet, doc);
516        if (ctx->transform == NULL) {
517            xmlFreeDoc(doc);
518            return NULL;
519        }
520
521        if (conf->params
522            && ngx_http_xslt_params(r, ctx, conf->params, 0) != NGX_OK)
523        {
524            xsltFreeTransformContext(ctx->transform);
525            xmlFreeDoc(doc);
526            return NULL;
527        }
528
529        if (ngx_http_xslt_params(r, ctx, &sheet[i].params, 1) != NGX_OK) {
530            xsltFreeTransformContext(ctx->transform);
531            xmlFreeDoc(doc);
532            return NULL;
533        }
534
535        res = xsltApplyStylesheetUser(sheet[i].stylesheet, doc,
536                                      ctx->params.elts, NULL, NULL,
537                                      ctx->transform);
538
539        xsltFreeTransformContext(ctx->transform);
540        xmlFreeDoc(doc);
541
542        if (res == NULL) {
543            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
544                          "xsltApplyStylesheet() failed");
545            return NULL;
546        }
547
548        doc = res;
549
550        /* reset array elements */
551        ctx->params.nelts = 0;
552    }
553
554    /* there must be at least one stylesheet */
555
556    if (r == r->main) {
557        type = ngx_http_xslt_content_type(sheet[i - 1].stylesheet);
558
559    } else {
560        type = NULL;
561    }
562
563    encoding = ngx_http_xslt_encoding(sheet[i - 1].stylesheet);
564    doc_type = doc->type;
565
566    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
567                   "xslt filter type: %d t:%s e:%s",
568                   doc_type, type ? type : (u_char *) "(null)",
569                   encoding ? encoding : (u_char *) "(null)");
570
571    rc = xsltSaveResultToString(&buf, &len, doc, sheet[i - 1].stylesheet);
572
573    xmlFreeDoc(doc);
574
575    if (rc != 0) {
576        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
577                      "xsltSaveResultToString() failed");
578        return NULL;
579    }
580
581    if (len == 0) {
582        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
583                      "xsltSaveResultToString() returned zero-length result");
584        return NULL;
585    }
586
587    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
588    if (b == NULL) {
589        ngx_free(buf);
590        return NULL;
591    }
592
593    b->pos = buf;
594    b->last = buf + len;
595    b->memory = 1;
596
597    if (encoding) {
598        r->headers_out.charset.len = ngx_strlen(encoding);
599        r->headers_out.charset.data = encoding;
600    }
601
602    if (r != r->main) {
603        return b;
604    }
605
606    b->last_buf = 1;
607
608    if (type) {
609        len = ngx_strlen(type);
610
611        r->headers_out.content_type_len = len;
612        r->headers_out.content_type.len = len;
613        r->headers_out.content_type.data = type;
614
615    } else if (doc_type == XML_HTML_DOCUMENT_NODE) {
616
617        r->headers_out.content_type_len = sizeof("text/html") - 1;
618        ngx_str_set(&r->headers_out.content_type, "text/html");
619    }
620
621    r->headers_out.content_type_lowcase = NULL;
622
623    return b;
624}
625
626
627static ngx_int_t
628ngx_http_xslt_params(ngx_http_request_t *r, ngx_http_xslt_filter_ctx_t *ctx,
629    ngx_array_t *params, ngx_uint_t final)
630{
631    u_char                 *p, *last, *value, *dst, *src, **s;
632    size_t                  len;
633    ngx_uint_t              i;
634    ngx_str_t               string;
635    ngx_http_xslt_param_t  *param;
636
637    param = params->elts;
638
639    for (i = 0; i < params->nelts; i++) {
640
641        if (ngx_http_complex_value(r, &param[i].value, &string) != NGX_OK) {
642            return NGX_ERROR;
643        }
644
645        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
646                       "xslt filter param: \"%s\"", string.data);
647
648        if (param[i].name) {
649
650            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
651                           "xslt filter param name: \"%s\"", param[i].name);
652
653            if (param[i].quote) {
654                if (xsltQuoteOneUserParam(ctx->transform, param[i].name,
655                                          string.data)
656                    != 0)
657                {
658                    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
659                                "xsltQuoteOneUserParam(\"%s\", \"%s\") failed",
660                                param[i].name, string.data);
661                    return NGX_ERROR;
662                }
663
664                continue;
665            }
666
667            s = ngx_array_push(&ctx->params);
668            if (s == NULL) {
669                return NGX_ERROR;
670            }
671
672            *s = param[i].name;
673
674            s = ngx_array_push(&ctx->params);
675            if (s == NULL) {
676                return NGX_ERROR;
677            }
678
679            *s = string.data;
680
681            continue;
682        }
683
684        /*
685         * parse param1=value1:param2=value2 syntax as used by parameters
686         * specified in xslt_stylesheet directives
687         */
688
689        p = string.data;
690        last = string.data + string.len;
691
692        while (p && *p) {
693
694            value = p;
695            p = (u_char *) ngx_strchr(p, '=');
696            if (p == NULL) {
697                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
698                                "invalid libxslt parameter \"%s\"", value);
699                return NGX_ERROR;
700            }
701            *p++ = '\0';
702
703            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
704                           "xslt filter param name: \"%s\"", value);
705
706            s = ngx_array_push(&ctx->params);
707            if (s == NULL) {
708                return NGX_ERROR;
709            }
710
711            *s = value;
712
713            value = p;
714            p = (u_char *) ngx_strchr(p, ':');
715
716            if (p) {
717                len = p - value;
718                *p++ = '\0';
719
720            } else {
721                len = last - value;
722            }
723
724            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
725                           "xslt filter param value: \"%s\"", value);
726
727            dst = value;
728            src = value;
729
730            ngx_unescape_uri(&dst, &src, len, 0);
731
732            *dst = '\0';
733
734            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
735                           "xslt filter param unescaped: \"%s\"", value);
736
737            s = ngx_array_push(&ctx->params);
738            if (s == NULL) {
739                return NGX_ERROR;
740            }
741
742            *s = value;
743        }
744    }
745
746    if (final) {
747        s = ngx_array_push(&ctx->params);
748        if (s == NULL) {
749            return NGX_ERROR;
750        }
751
752        *s = NULL;
753    }
754
755    return NGX_OK;
756}
757
758
759static u_char *
760ngx_http_xslt_content_type(xsltStylesheetPtr s)
761{
762    u_char  *type;
763
764    if (s->mediaType) {
765        return s->mediaType;
766    }
767
768    for (s = s->imports; s; s = s->next) {
769
770        type = ngx_http_xslt_content_type(s);
771
772        if (type) {
773            return type;
774        }
775    }
776
777    return NULL;
778}
779
780
781static u_char *
782ngx_http_xslt_encoding(xsltStylesheetPtr s)
783{
784    u_char  *encoding;
785
786    if (s->encoding) {
787        return s->encoding;
788    }
789
790    for (s = s->imports; s; s = s->next) {
791
792        encoding = ngx_http_xslt_encoding(s);
793
794        if (encoding) {
795            return encoding;
796        }
797    }
798
799    return NULL;
800}
801
802
803static void
804ngx_http_xslt_cleanup(void *data)
805{
806    ngx_free(data);
807}
808
809
810static char *
811ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
812{
813    ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
814
815    ngx_str_t                         *value;
816    ngx_uint_t                         i;
817    ngx_pool_cleanup_t                *cln;
818    ngx_http_xslt_file_t              *file;
819    ngx_http_xslt_filter_main_conf_t  *xmcf;
820
821    if (xlcf->dtd) {
822        return "is duplicate";
823    }
824
825    value = cf->args->elts;
826
827    xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
828
829    file = xmcf->dtd_files.elts;
830    for (i = 0; i < xmcf->dtd_files.nelts; i++) {
831        if (ngx_strcmp(file[i].name, value[1].data) == 0) {
832            xlcf->dtd = file[i].data;
833            return NGX_CONF_OK;
834        }
835    }
836
837    cln = ngx_pool_cleanup_add(cf->pool, 0);
838    if (cln == NULL) {
839        return NGX_CONF_ERROR;
840    }
841
842    xlcf->dtd = xmlParseDTD(NULL, (xmlChar *) value[1].data);
843
844    if (xlcf->dtd == NULL) {
845        ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "xmlParseDTD() failed");
846        return NGX_CONF_ERROR;
847    }
848
849    cln->handler = ngx_http_xslt_cleanup_dtd;
850    cln->data = xlcf->dtd;
851
852    file = ngx_array_push(&xmcf->dtd_files);
853    if (file == NULL) {
854        return NGX_CONF_ERROR;
855    }
856
857    file->name = value[1].data;
858    file->data = xlcf->dtd;
859
860    return NGX_CONF_OK;
861}
862
863
864
865static char *
866ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
867{
868    ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
869
870    ngx_str_t                         *value;
871    ngx_uint_t                         i, n;
872    ngx_pool_cleanup_t                *cln;
873    ngx_http_xslt_file_t              *file;
874    ngx_http_xslt_sheet_t             *sheet;
875    ngx_http_xslt_param_t             *param;
876    ngx_http_compile_complex_value_t   ccv;
877    ngx_http_xslt_filter_main_conf_t  *xmcf;
878
879    value = cf->args->elts;
880
881    if (xlcf->sheets.elts == NULL) {
882        if (ngx_array_init(&xlcf->sheets, cf->pool, 1,
883                           sizeof(ngx_http_xslt_sheet_t))
884            != NGX_OK)
885        {
886            return NGX_CONF_ERROR;
887        }
888    }
889
890    sheet = ngx_array_push(&xlcf->sheets);
891    if (sheet == NULL) {
892        return NGX_CONF_ERROR;
893    }
894
895    ngx_memzero(sheet, sizeof(ngx_http_xslt_sheet_t));
896
897    if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
898        return NGX_CONF_ERROR;
899    }
900
901    xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
902
903    file = xmcf->sheet_files.elts;
904    for (i = 0; i < xmcf->sheet_files.nelts; i++) {
905        if (ngx_strcmp(file[i].name, value[1].data) == 0) {
906            sheet->stylesheet = file[i].data;
907            goto found;
908        }
909    }
910
911    cln = ngx_pool_cleanup_add(cf->pool, 0);
912    if (cln == NULL) {
913        return NGX_CONF_ERROR;
914    }
915
916    sheet->stylesheet = xsltParseStylesheetFile(value[1].data);
917    if (sheet->stylesheet == NULL) {
918        ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
919                           "xsltParseStylesheetFile(\"%s\") failed",
920                           value[1].data);
921        return NGX_CONF_ERROR;
922    }
923
924    cln->handler = ngx_http_xslt_cleanup_stylesheet;
925    cln->data = sheet->stylesheet;
926
927    file = ngx_array_push(&xmcf->sheet_files);
928    if (file == NULL) {
929        return NGX_CONF_ERROR;
930    }
931
932    file->name = value[1].data;
933    file->data = sheet->stylesheet;
934
935found:
936
937    n = cf->args->nelts;
938
939    if (n == 2) {
940        return NGX_CONF_OK;
941    }
942
943    if (ngx_array_init(&sheet->params, cf->pool, n - 2,
944                       sizeof(ngx_http_xslt_param_t))
945        != NGX_OK)
946    {
947        return NGX_CONF_ERROR;
948    }
949
950    for (i = 2; i < n; i++) {
951
952        param = ngx_array_push(&sheet->params);
953        if (param == NULL) {
954            return NGX_CONF_ERROR;
955        }
956
957        ngx_memzero(param, sizeof(ngx_http_xslt_param_t));
958        ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
959
960        ccv.cf = cf;
961        ccv.value = &value[i];
962        ccv.complex_value = &param->value;
963        ccv.zero = 1;
964
965        if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
966            return NGX_CONF_ERROR;
967        }
968    }
969
970    return NGX_CONF_OK;
971}
972
973
974static char *
975ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
976{
977    ngx_http_xslt_filter_loc_conf_t  *xlcf = conf;
978
979    ngx_http_xslt_param_t            *param;
980    ngx_http_compile_complex_value_t  ccv;
981    ngx_str_t                        *value;
982
983    value = cf->args->elts;
984
985    if (xlcf->params == NULL) {
986        xlcf->params = ngx_array_create(cf->pool, 2,
987                                        sizeof(ngx_http_xslt_param_t));
988        if (xlcf->params == NULL) {
989            return NGX_CONF_ERROR;
990        }
991    }
992
993    param = ngx_array_push(xlcf->params);
994    if (param == NULL) {
995        return NGX_CONF_ERROR;
996    }
997
998    param->name = value[1].data;
999    param->quote = (cmd->post == NULL) ? 0 : 1;
1000
1001    ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1002
1003    ccv.cf = cf;
1004    ccv.value = &value[2];
1005    ccv.complex_value = &param->value;
1006    ccv.zero = 1;
1007
1008    if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1009        return NGX_CONF_ERROR;
1010    }
1011
1012    return NGX_CONF_OK;
1013}
1014
1015
1016static void
1017ngx_http_xslt_cleanup_dtd(void *data)
1018{
1019    xmlFreeDtd(data);
1020}
1021
1022
1023static void
1024ngx_http_xslt_cleanup_stylesheet(void *data)
1025{
1026    xsltFreeStylesheet(data);
1027}
1028
1029
1030static void *
1031ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf)
1032{
1033    ngx_http_xslt_filter_main_conf_t  *conf;
1034
1035    conf = ngx_palloc(cf->pool, sizeof(ngx_http_xslt_filter_main_conf_t));
1036    if (conf == NULL) {
1037        return NULL;
1038    }
1039
1040    if (ngx_array_init(&conf->dtd_files, cf->pool, 1,
1041                       sizeof(ngx_http_xslt_file_t))
1042        != NGX_OK)
1043    {
1044        return NULL;
1045    }
1046
1047    if (ngx_array_init(&conf->sheet_files, cf->pool, 1,
1048                       sizeof(ngx_http_xslt_file_t))
1049        != NGX_OK)
1050    {
1051        return NULL;
1052    }
1053
1054    return conf;
1055}
1056
1057
1058static void *
1059ngx_http_xslt_filter_create_conf(ngx_conf_t *cf)
1060{
1061    ngx_http_xslt_filter_loc_conf_t  *conf;
1062
1063    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xslt_filter_loc_conf_t));
1064    if (conf == NULL) {
1065        return NULL;
1066    }
1067
1068    /*
1069     * set by ngx_pcalloc():
1070     *
1071     *     conf->dtd = NULL;
1072     *     conf->sheets = { NULL };
1073     *     conf->types = { NULL };
1074     *     conf->types_keys = NULL;
1075     *     conf->params = NULL;
1076     */
1077
1078    conf->last_modified = NGX_CONF_UNSET;
1079
1080    return conf;
1081}
1082
1083
1084static char *
1085ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
1086{
1087    ngx_http_xslt_filter_loc_conf_t *prev = parent;
1088    ngx_http_xslt_filter_loc_conf_t *conf = child;
1089
1090    if (conf->dtd == NULL) {
1091        conf->dtd = prev->dtd;
1092    }
1093
1094    if (conf->sheets.nelts == 0) {
1095        conf->sheets = prev->sheets;
1096    }
1097
1098    if (conf->params == NULL) {
1099        conf->params = prev->params;
1100    }
1101
1102    if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
1103                             &prev->types_keys, &prev->types,
1104                             ngx_http_xslt_default_types)
1105        != NGX_OK)
1106    {
1107        return NGX_CONF_ERROR;
1108    }
1109
1110    ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
1111
1112    return NGX_CONF_OK;
1113}
1114
1115
1116static ngx_int_t
1117ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf)
1118{
1119    xmlInitParser();
1120
1121#if (NGX_HAVE_EXSLT)
1122    exsltRegisterAll();
1123#endif
1124
1125    return NGX_OK;
1126}
1127
1128
1129static ngx_int_t
1130ngx_http_xslt_filter_init(ngx_conf_t *cf)
1131{
1132    ngx_http_next_header_filter = ngx_http_top_header_filter;
1133    ngx_http_top_header_filter = ngx_http_xslt_header_filter;
1134
1135    ngx_http_next_body_filter = ngx_http_top_body_filter;
1136    ngx_http_top_body_filter = ngx_http_xslt_body_filter;
1137
1138    return NGX_OK;
1139}
1140
1141
1142static void
1143ngx_http_xslt_filter_exit(ngx_cycle_t *cycle)
1144{
1145    xsltCleanupGlobals();
1146    xmlCleanupParser();
1147}
1148