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