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
ngx_http_xslt_header_filter(ngx_http_request_t * r)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
ngx_http_xslt_body_filter(ngx_http_request_t * r,ngx_chain_t * in)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
ngx_http_xslt_send(ngx_http_request_t * r,ngx_http_xslt_filter_ctx_t * ctx,ngx_buf_t * b)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
ngx_http_xslt_add_chunk(ngx_http_request_t * r,ngx_http_xslt_filter_ctx_t * ctx,ngx_buf_t * b)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
ngx_http_xslt_sax_external_subset(void * data,const xmlChar * name,const xmlChar * externalId,const xmlChar * systemId)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
ngx_http_xslt_sax_error(void * data,const char * msg,...)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 *
ngx_http_xslt_apply_stylesheet(ngx_http_request_t * r,ngx_http_xslt_filter_ctx_t * ctx)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_calloc_buf(r->pool);
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
ngx_http_xslt_params(ngx_http_request_t * r,ngx_http_xslt_filter_ctx_t * ctx,ngx_array_t * params,ngx_uint_t final)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         if (param[i].value.lengths) {
690             p = string.data;
691 
692         } else {
693             p = ngx_pnalloc(r->pool, string.len + 1);
694             if (p == NULL) {
695                 return NGX_ERROR;
696             }
697 
698             ngx_memcpy(p, string.data, string.len + 1);
699         }
700 
701         last = p + string.len;
702 
703         while (p && *p) {
704 
705             value = p;
706             p = (u_char *) ngx_strchr(p, '=');
707             if (p == NULL) {
708                 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
709                                 "invalid libxslt parameter \"%s\"", value);
710                 return NGX_ERROR;
711             }
712             *p++ = '\0';
713 
714             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
715                            "xslt filter param name: \"%s\"", value);
716 
717             s = ngx_array_push(&ctx->params);
718             if (s == NULL) {
719                 return NGX_ERROR;
720             }
721 
722             *s = value;
723 
724             value = p;
725             p = (u_char *) ngx_strchr(p, ':');
726 
727             if (p) {
728                 len = p - value;
729                 *p++ = '\0';
730 
731             } else {
732                 len = last - value;
733             }
734 
735             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
736                            "xslt filter param value: \"%s\"", value);
737 
738             dst = value;
739             src = value;
740 
741             ngx_unescape_uri(&dst, &src, len, 0);
742 
743             *dst = '\0';
744 
745             ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
746                            "xslt filter param unescaped: \"%s\"", value);
747 
748             s = ngx_array_push(&ctx->params);
749             if (s == NULL) {
750                 return NGX_ERROR;
751             }
752 
753             *s = value;
754         }
755     }
756 
757     if (final) {
758         s = ngx_array_push(&ctx->params);
759         if (s == NULL) {
760             return NGX_ERROR;
761         }
762 
763         *s = NULL;
764     }
765 
766     return NGX_OK;
767 }
768 
769 
770 static u_char *
ngx_http_xslt_content_type(xsltStylesheetPtr s)771 ngx_http_xslt_content_type(xsltStylesheetPtr s)
772 {
773     u_char  *type;
774 
775     if (s->mediaType) {
776         return s->mediaType;
777     }
778 
779     for (s = s->imports; s; s = s->next) {
780 
781         type = ngx_http_xslt_content_type(s);
782 
783         if (type) {
784             return type;
785         }
786     }
787 
788     return NULL;
789 }
790 
791 
792 static u_char *
ngx_http_xslt_encoding(xsltStylesheetPtr s)793 ngx_http_xslt_encoding(xsltStylesheetPtr s)
794 {
795     u_char  *encoding;
796 
797     if (s->encoding) {
798         return s->encoding;
799     }
800 
801     for (s = s->imports; s; s = s->next) {
802 
803         encoding = ngx_http_xslt_encoding(s);
804 
805         if (encoding) {
806             return encoding;
807         }
808     }
809 
810     return NULL;
811 }
812 
813 
814 static void
ngx_http_xslt_cleanup(void * data)815 ngx_http_xslt_cleanup(void *data)
816 {
817     ngx_free(data);
818 }
819 
820 
821 static char *
ngx_http_xslt_entities(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)822 ngx_http_xslt_entities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
823 {
824     ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
825 
826     ngx_str_t                         *value;
827     ngx_uint_t                         i;
828     ngx_pool_cleanup_t                *cln;
829     ngx_http_xslt_file_t              *file;
830     ngx_http_xslt_filter_main_conf_t  *xmcf;
831 
832     if (xlcf->dtd) {
833         return "is duplicate";
834     }
835 
836     value = cf->args->elts;
837 
838     xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
839 
840     file = xmcf->dtd_files.elts;
841     for (i = 0; i < xmcf->dtd_files.nelts; i++) {
842         if (ngx_strcmp(file[i].name, value[1].data) == 0) {
843             xlcf->dtd = file[i].data;
844             return NGX_CONF_OK;
845         }
846     }
847 
848     cln = ngx_pool_cleanup_add(cf->pool, 0);
849     if (cln == NULL) {
850         return NGX_CONF_ERROR;
851     }
852 
853     xlcf->dtd = xmlParseDTD(NULL, (xmlChar *) value[1].data);
854 
855     if (xlcf->dtd == NULL) {
856         ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "xmlParseDTD() failed");
857         return NGX_CONF_ERROR;
858     }
859 
860     cln->handler = ngx_http_xslt_cleanup_dtd;
861     cln->data = xlcf->dtd;
862 
863     file = ngx_array_push(&xmcf->dtd_files);
864     if (file == NULL) {
865         return NGX_CONF_ERROR;
866     }
867 
868     file->name = value[1].data;
869     file->data = xlcf->dtd;
870 
871     return NGX_CONF_OK;
872 }
873 
874 
875 
876 static char *
ngx_http_xslt_stylesheet(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)877 ngx_http_xslt_stylesheet(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
878 {
879     ngx_http_xslt_filter_loc_conf_t *xlcf = conf;
880 
881     ngx_str_t                         *value;
882     ngx_uint_t                         i, n;
883     ngx_pool_cleanup_t                *cln;
884     ngx_http_xslt_file_t              *file;
885     ngx_http_xslt_sheet_t             *sheet;
886     ngx_http_xslt_param_t             *param;
887     ngx_http_compile_complex_value_t   ccv;
888     ngx_http_xslt_filter_main_conf_t  *xmcf;
889 
890     value = cf->args->elts;
891 
892     if (xlcf->sheets.elts == NULL) {
893         if (ngx_array_init(&xlcf->sheets, cf->pool, 1,
894                            sizeof(ngx_http_xslt_sheet_t))
895             != NGX_OK)
896         {
897             return NGX_CONF_ERROR;
898         }
899     }
900 
901     sheet = ngx_array_push(&xlcf->sheets);
902     if (sheet == NULL) {
903         return NGX_CONF_ERROR;
904     }
905 
906     ngx_memzero(sheet, sizeof(ngx_http_xslt_sheet_t));
907 
908     if (ngx_conf_full_name(cf->cycle, &value[1], 0) != NGX_OK) {
909         return NGX_CONF_ERROR;
910     }
911 
912     xmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_xslt_filter_module);
913 
914     file = xmcf->sheet_files.elts;
915     for (i = 0; i < xmcf->sheet_files.nelts; i++) {
916         if (ngx_strcmp(file[i].name, value[1].data) == 0) {
917             sheet->stylesheet = file[i].data;
918             goto found;
919         }
920     }
921 
922     cln = ngx_pool_cleanup_add(cf->pool, 0);
923     if (cln == NULL) {
924         return NGX_CONF_ERROR;
925     }
926 
927     sheet->stylesheet = xsltParseStylesheetFile(value[1].data);
928     if (sheet->stylesheet == NULL) {
929         ngx_conf_log_error(NGX_LOG_ERR, cf, 0,
930                            "xsltParseStylesheetFile(\"%s\") failed",
931                            value[1].data);
932         return NGX_CONF_ERROR;
933     }
934 
935     cln->handler = ngx_http_xslt_cleanup_stylesheet;
936     cln->data = sheet->stylesheet;
937 
938     file = ngx_array_push(&xmcf->sheet_files);
939     if (file == NULL) {
940         return NGX_CONF_ERROR;
941     }
942 
943     file->name = value[1].data;
944     file->data = sheet->stylesheet;
945 
946 found:
947 
948     n = cf->args->nelts;
949 
950     if (n == 2) {
951         return NGX_CONF_OK;
952     }
953 
954     if (ngx_array_init(&sheet->params, cf->pool, n - 2,
955                        sizeof(ngx_http_xslt_param_t))
956         != NGX_OK)
957     {
958         return NGX_CONF_ERROR;
959     }
960 
961     for (i = 2; i < n; i++) {
962 
963         param = ngx_array_push(&sheet->params);
964         if (param == NULL) {
965             return NGX_CONF_ERROR;
966         }
967 
968         ngx_memzero(param, sizeof(ngx_http_xslt_param_t));
969         ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
970 
971         ccv.cf = cf;
972         ccv.value = &value[i];
973         ccv.complex_value = &param->value;
974         ccv.zero = 1;
975 
976         if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
977             return NGX_CONF_ERROR;
978         }
979     }
980 
981     return NGX_CONF_OK;
982 }
983 
984 
985 static char *
ngx_http_xslt_param(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)986 ngx_http_xslt_param(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
987 {
988     ngx_http_xslt_filter_loc_conf_t  *xlcf = conf;
989 
990     ngx_http_xslt_param_t            *param;
991     ngx_http_compile_complex_value_t  ccv;
992     ngx_str_t                        *value;
993 
994     value = cf->args->elts;
995 
996     if (xlcf->params == NULL) {
997         xlcf->params = ngx_array_create(cf->pool, 2,
998                                         sizeof(ngx_http_xslt_param_t));
999         if (xlcf->params == NULL) {
1000             return NGX_CONF_ERROR;
1001         }
1002     }
1003 
1004     param = ngx_array_push(xlcf->params);
1005     if (param == NULL) {
1006         return NGX_CONF_ERROR;
1007     }
1008 
1009     param->name = value[1].data;
1010     param->quote = (cmd->post == NULL) ? 0 : 1;
1011 
1012     ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
1013 
1014     ccv.cf = cf;
1015     ccv.value = &value[2];
1016     ccv.complex_value = &param->value;
1017     ccv.zero = 1;
1018 
1019     if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
1020         return NGX_CONF_ERROR;
1021     }
1022 
1023     return NGX_CONF_OK;
1024 }
1025 
1026 
1027 static void
ngx_http_xslt_cleanup_dtd(void * data)1028 ngx_http_xslt_cleanup_dtd(void *data)
1029 {
1030     xmlFreeDtd(data);
1031 }
1032 
1033 
1034 static void
ngx_http_xslt_cleanup_stylesheet(void * data)1035 ngx_http_xslt_cleanup_stylesheet(void *data)
1036 {
1037     xsltFreeStylesheet(data);
1038 }
1039 
1040 
1041 static void *
ngx_http_xslt_filter_create_main_conf(ngx_conf_t * cf)1042 ngx_http_xslt_filter_create_main_conf(ngx_conf_t *cf)
1043 {
1044     ngx_http_xslt_filter_main_conf_t  *conf;
1045 
1046     conf = ngx_palloc(cf->pool, sizeof(ngx_http_xslt_filter_main_conf_t));
1047     if (conf == NULL) {
1048         return NULL;
1049     }
1050 
1051     if (ngx_array_init(&conf->dtd_files, cf->pool, 1,
1052                        sizeof(ngx_http_xslt_file_t))
1053         != NGX_OK)
1054     {
1055         return NULL;
1056     }
1057 
1058     if (ngx_array_init(&conf->sheet_files, cf->pool, 1,
1059                        sizeof(ngx_http_xslt_file_t))
1060         != NGX_OK)
1061     {
1062         return NULL;
1063     }
1064 
1065     return conf;
1066 }
1067 
1068 
1069 static void *
ngx_http_xslt_filter_create_conf(ngx_conf_t * cf)1070 ngx_http_xslt_filter_create_conf(ngx_conf_t *cf)
1071 {
1072     ngx_http_xslt_filter_loc_conf_t  *conf;
1073 
1074     conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_xslt_filter_loc_conf_t));
1075     if (conf == NULL) {
1076         return NULL;
1077     }
1078 
1079     /*
1080      * set by ngx_pcalloc():
1081      *
1082      *     conf->dtd = NULL;
1083      *     conf->sheets = { NULL };
1084      *     conf->types = { NULL };
1085      *     conf->types_keys = NULL;
1086      *     conf->params = NULL;
1087      */
1088 
1089     conf->last_modified = NGX_CONF_UNSET;
1090 
1091     return conf;
1092 }
1093 
1094 
1095 static char *
ngx_http_xslt_filter_merge_conf(ngx_conf_t * cf,void * parent,void * child)1096 ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child)
1097 {
1098     ngx_http_xslt_filter_loc_conf_t *prev = parent;
1099     ngx_http_xslt_filter_loc_conf_t *conf = child;
1100 
1101     if (conf->dtd == NULL) {
1102         conf->dtd = prev->dtd;
1103     }
1104 
1105     if (conf->sheets.nelts == 0) {
1106         conf->sheets = prev->sheets;
1107     }
1108 
1109     if (conf->params == NULL) {
1110         conf->params = prev->params;
1111     }
1112 
1113     if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
1114                              &prev->types_keys, &prev->types,
1115                              ngx_http_xslt_default_types)
1116         != NGX_OK)
1117     {
1118         return NGX_CONF_ERROR;
1119     }
1120 
1121     ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
1122 
1123     return NGX_CONF_OK;
1124 }
1125 
1126 
1127 static ngx_int_t
ngx_http_xslt_filter_preconfiguration(ngx_conf_t * cf)1128 ngx_http_xslt_filter_preconfiguration(ngx_conf_t *cf)
1129 {
1130     xmlInitParser();
1131 
1132 #if (NGX_HAVE_EXSLT)
1133     exsltRegisterAll();
1134 #endif
1135 
1136     return NGX_OK;
1137 }
1138 
1139 
1140 static ngx_int_t
ngx_http_xslt_filter_init(ngx_conf_t * cf)1141 ngx_http_xslt_filter_init(ngx_conf_t *cf)
1142 {
1143     ngx_http_next_header_filter = ngx_http_top_header_filter;
1144     ngx_http_top_header_filter = ngx_http_xslt_header_filter;
1145 
1146     ngx_http_next_body_filter = ngx_http_top_body_filter;
1147     ngx_http_top_body_filter = ngx_http_xslt_body_filter;
1148 
1149     return NGX_OK;
1150 }
1151 
1152 
1153 static void
ngx_http_xslt_filter_exit(ngx_cycle_t * cycle)1154 ngx_http_xslt_filter_exit(ngx_cycle_t *cycle)
1155 {
1156     xsltCleanupGlobals();
1157     xmlCleanupParser();
1158 }
1159