Integrate TLDK with NGINX
[tldk.git] / app / nginx / src / http / modules / ngx_http_xslt_filter_module.c
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
30 typedef struct {
31     u_char                    *name;
32     void                      *data;
33 } ngx_http_xslt_file_t;
34
35
36 typedef 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
42 typedef 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
49 typedef struct {
50     xsltStylesheetPtr          stylesheet;
51     ngx_array_t                params;       /* ngx_http_xslt_param_t */
52 } ngx_http_xslt_sheet_t;
53
54
55 typedef 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
65 typedef 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
76 static ngx_int_t ngx_http_xslt_send(ngx_http_request_t *r,
77     ngx_http_xslt_filter_ctx_t *ctx, ngx_buf_t *b);
78 static 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
82 static void ngx_http_xslt_sax_external_subset(void *data, const xmlChar *name,
83     const xmlChar *externalId, const xmlChar *systemId);
84 static void ngx_cdecl ngx_http_xslt_sax_error(void *data, const char *msg, ...);
85
86
87 static ngx_buf_t *ngx_http_xslt_apply_stylesheet(ngx_http_request_t *r,
88     ngx_http_xslt_filter_ctx_t *ctx);
89 static 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);
91 static u_char *ngx_http_xslt_content_type(xsltStylesheetPtr s);
92 static u_char *ngx_http_xslt_encoding(xsltStylesheetPtr s);
93 static void ngx_http_xslt_cleanup(void *data);
94
95 static char *ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd,
96     void *conf);
97 static char *ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd,
98     void *conf);
99 static char *ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd,
100     void *conf);
101 static void ngx_http_xslt_cleanup_dtd(void *data);
102 static void ngx_http_xslt_cleanup_stylesheet(void *data);
103 static void *ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf);
104 static void *ngx_http_xslt_filter_create_conf(ngx_conf_t *cf);
105 static char *ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent,
106     void *child);
107 static ngx_int_t ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf);
108 static ngx_int_t ngx_http_xslt_filter_init(ngx_conf_t *cf);
109 static void ngx_http_xslt_filter_exit(ngx_cycle_t *cycle);
110
111
112 static ngx_str_t  ngx_http_xslt_default_types[] = {
113     ngx_string("text/xml"),
114     ngx_null_string
115 };
116
117
118 static 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
166 static 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
181 ngx_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
197 static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
198 static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
199
200
201 static ngx_int_t
202 ngx_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
241 static ngx_int_t
242 ngx_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
308 static ngx_int_t
309 ngx_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
368 static ngx_int_t
369 ngx_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
411 static void
412 ngx_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
462 static void ngx_cdecl
463 ngx_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
487 static ngx_buf_t *
488 ngx_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
627 static ngx_int_t
628 ngx_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
759 static u_char *
760 ngx_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
781 static u_char *
782 ngx_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
803 static void
804 ngx_http_xslt_cleanup(void *data)
805 {
806     ngx_free(data);
807 }
808
809
810 static char *
811 ngx_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
865 static char *
866 ngx_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
935 found:
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
974 static char *
975 ngx_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
1016 static void
1017 ngx_http_xslt_cleanup_dtd(void *data)
1018 {
1019     xmlFreeDtd(data);
1020 }
1021
1022
1023 static void
1024 ngx_http_xslt_cleanup_stylesheet(void *data)
1025 {
1026     xsltFreeStylesheet(data);
1027 }
1028
1029
1030 static void *
1031 ngx_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
1058 static void *
1059 ngx_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
1084 static char *
1085 ngx_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
1116 static ngx_int_t
1117 ngx_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
1129 static ngx_int_t
1130 ngx_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
1142 static void
1143 ngx_http_xslt_filter_exit(ngx_cycle_t *cycle)
1144 {
1145     xsltCleanupGlobals();
1146     xmlCleanupParser();
1147 }