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, ¶m[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 = ¶m->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 = ¶m->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