Hush a warning about XML_Size not being an int
[Evergreen.git] / Open-ILS / src / apachemods / mod_xmlent.c
1 #include "httpd.h"
2 #include "http_config.h"
3 #include "http_core.h"
4 #include "http_protocol.h"
5 #include "http_request.h"
6 //#include "apr_compat.h"
7 #include "apr_strings.h"
8 #include "apr_reslist.h"
9 #include "http_log.h"
10 #include "util_filter.h"
11 #include "opensrf/utils.h"
12
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <expat.h>
16
17 #define MODULE_NAME     "xmlent_module"
18
19 /* Define the config defaults here */
20 #define MODXMLENT_CONFIG_STRIP_COMMENTS "XMLEntStripComments" 
21 #define MODXMLENT_CONFIG_CONTENT_TYPE "XMLEntContentType"
22 #define MODXMLENT_CONFIG_CONTENT_TYPE_DEFAULT "text/html"
23 #define MODXMLENT_CONFIG_STRIP_PI "XMLEntStripPI"  
24 #define MODXMLENT_CONFIG_DOCTYPE "XMLEntDoctype"
25 #define MODXMLENT_CONFIG_STRIP_DOCTYPE "XMLEntStripDoctype"
26 #define MODXMLENT_CONFIG_ESCAPE_SCRIPT "XMLEntEscapeScript"
27
28 module AP_MODULE_DECLARE_DATA xmlent_module;
29
30 int xmlEntInScript = 0; /* are we in the middle of a <script> tag */
31
32 /* our context */
33 typedef struct {
34         apr_bucket_brigade* brigade; /* the bucket brigade we buffer our data into */
35         XML_Parser parser; /* our XML parser */
36 } xmlEntContext;
37
38 /* our config data */
39 typedef struct {
40         int stripComments;      /* should we strip comments on the way out? */
41         int stripPI;                    /* should we strip processing instructions on the way out? */
42         int stripDoctype;
43         int escapeScript;               /* if true, we html-escape anything text inside a <scritp> tag */
44         char* contentType;      /* the content type used to server pages */
45         char* doctype;                  /* the doctype header to send before any other data */
46 } xmlEntConfig;
47
48
49 /* get the content type from the config */
50 static const char* xmlEntSetContentType(cmd_parms *params, void *cfg, const char *arg) {
51         xmlEntConfig* config = (xmlEntConfig*) cfg;
52         config->contentType = (char*) arg;
53         return NULL;
54 }
55
56
57 /* get the stip PI flag from the config */
58 static const char* xmlEntSetStripPI(cmd_parms *params, void *cfg, const char *arg) {
59         xmlEntConfig* config = (xmlEntConfig*) cfg;
60         char* a = (char*) arg;
61         config->stripPI = (a && !strcasecmp(a, "yes")) ? 1 : 0;
62         return NULL;
63 }
64
65 /* Get the strip comments flag from the config */
66 static const char* xmlEntSetStripComments(cmd_parms *params, void *cfg, const char *arg) {
67         xmlEntConfig* config = (xmlEntConfig*) cfg;
68         char* a = (char*) arg;
69         config->stripComments = (a && !strcasecmp(a, "yes")) ? 1 : 0;
70         return NULL;
71 }
72
73 static const char* xmlEntSetEscapeScript(cmd_parms *params, void *cfg, const char *arg) {
74         xmlEntConfig* config = (xmlEntConfig*) cfg;
75         char* a = (char*) arg;
76         config->escapeScript = (a && !strcasecmp(a, "yes")) ? 1 : 0;
77         return NULL;
78 }
79
80 static const char* xmlEntSetStripDoctype(cmd_parms *params, void *cfg, const char *arg) {
81         xmlEntConfig* config = (xmlEntConfig*) cfg;
82         char* a = (char*) arg;
83         config->stripDoctype = (a && !strcasecmp(a, "yes")) ? 1 : 0;
84         return NULL;
85 }
86
87
88 /* Get the user defined doctype from the config */
89 static const char* xmlEntSetDoctype(cmd_parms *params, void *cfg, const char *arg) {
90         xmlEntConfig* config = (xmlEntConfig*) cfg;
91         config->doctype = (char*) arg;
92         return NULL;
93 }
94
95 /* Tell apache how to set our config variables */
96 static const command_rec xmlEntCommands[] = {
97         AP_INIT_TAKE1( MODXMLENT_CONFIG_STRIP_COMMENTS, 
98                         xmlEntSetStripComments, NULL, ACCESS_CONF, "XMLENT Strip Comments"),
99         AP_INIT_TAKE1( MODXMLENT_CONFIG_CONTENT_TYPE, 
100                         xmlEntSetContentType, NULL, ACCESS_CONF, "XMLENT Content Type"),
101         AP_INIT_TAKE1( MODXMLENT_CONFIG_STRIP_PI,
102                         xmlEntSetStripPI, NULL, ACCESS_CONF, "XMLENT Strip XML Processing Instructions"),
103         AP_INIT_TAKE1( MODXMLENT_CONFIG_DOCTYPE,
104                         xmlEntSetDoctype, NULL, ACCESS_CONF, "XMLENT Doctype Declaration"),
105         AP_INIT_TAKE1( MODXMLENT_CONFIG_STRIP_DOCTYPE,
106                         xmlEntSetStripDoctype, NULL, ACCESS_CONF, "XMLENT Strip Doctype Declaration"),
107         AP_INIT_TAKE1( MODXMLENT_CONFIG_ESCAPE_SCRIPT,
108                         xmlEntSetEscapeScript, NULL, ACCESS_CONF, "XMLENT Escape data in script tags"),
109         {NULL}
110 };
111
112 /* Creates a new config object */
113 static void* xmlEntCreateDirConfig( apr_pool_t* p, char* dir ) {
114         xmlEntConfig* config = 
115                 (xmlEntConfig*) apr_palloc( p, sizeof(xmlEntConfig) );
116
117         config->stripComments = 0;
118         config->stripPI       = 0;
119         config->stripDoctype  = 0;
120         config->escapeScript     = 1;
121         config->contentType      = MODXMLENT_CONFIG_CONTENT_TYPE_DEFAULT;
122         config->doctype       = NULL;
123
124         return (void*) config;
125 }
126
127 /* keep for a while in case we ever need it */
128 /*
129 #define XMLENT_INHERIT(p, c, f) ((c->f) ? c->f : p->f);
130 static void* xmlEntMergeDirConfig(apr_pool_t *p, void *base, void *overrides) {
131         xmlEntConfig* parent            = base;
132         xmlEntConfig* child             = overrides;
133         xmlEntConfig* newConf   = (xmlEntConfig*) apr_pcalloc(p, sizeof(xmlEntConfig));
134         newConf->contentType = XMLENT_INHERIT(parent, child, contentType);
135         newConf->stripComments = XMLENT_INHERIT(parent, child, stripComments);
136         return newConf;
137 }
138 */
139
140
141 /* We need a global parser object because sub-requests, with different
142  * filter contexts, are parsing part of the same document.
143  * This means that this filter will only work in forked (non-threaded) environments.
144  * XXX Figure out how to share pointers/data accross filters */
145 XML_Parser parser = NULL;
146
147 /* utility function which passes data to the next filter */
148 static void _fwrite( ap_filter_t* filter, char* data, ... ) {
149         if(!(filter && data)) return;
150         xmlEntContext* ctx = (xmlEntContext*) filter->ctx;
151         VA_LIST_TO_STRING(data);
152         ap_fwrite( filter->next, ctx->brigade, VA_BUF, strlen(VA_BUF));
153 }
154
155
156 /** XXX move me to  opensrf/utils.h */
157 #define OSRF_UTILS_REPLACE_CHAR(str, o, n)\
158         do {\
159                 int i = 0;\
160                 while(str[i] != '\0') {\
161                         if(str[i] == o)\
162                                 str[i] = n;\
163                         i++;\
164                 }\
165         } while(0)
166
167 /* cycles through the attributes attached to an element */
168 static void printAttr( ap_filter_t* filter, const char** atts ) {
169         if(!atts) return;
170         int i;
171         for( i = 0; atts[i] && atts[i+1]; i++ ) {
172                 const char* name = atts[i];
173                 const char* value = atts[i+1];
174                 char* escaped = ap_escape_html(filter->r->pool, value); 
175
176                 /* we make a big assumption here that if the string contains a ', 
177                  * then the original attribute was wrapped in "s - so recreate that */
178                 if( strchr( escaped, '\'' ) ) {
179                         OSRF_UTILS_REPLACE_CHAR(escaped,'"','\'');
180                         _fwrite( filter, " %s=\"%s\"", name, escaped );
181
182                 } else {
183                         OSRF_UTILS_REPLACE_CHAR(escaped,'\'','"');
184                         _fwrite( filter, " %s='%s'", name, escaped );
185                 }
186
187                 i++;
188         }
189 }
190
191 /* Starts an XML element */
192 static void XMLCALL startElement(void *userData, const char *name, const char **atts) {
193         ap_filter_t* filter = (ap_filter_t*) userData;
194         _fwrite(filter, "<%s", name );
195         printAttr( filter, atts );
196         _fwrite(filter, ">", name );
197         if(!strcmp(name, "script")) 
198                 xmlEntInScript = 1;
199 }
200
201 /* Handles the character data */
202 static void XMLCALL charHandler( void* userData, const XML_Char* s, int len ) {
203         ap_filter_t* filter = (ap_filter_t*) userData;
204         char data[len+1];
205         bzero(data, len+1);
206         memcpy( data, s, len );
207
208         xmlEntConfig* config = ap_get_module_config( 
209                         filter->r->per_dir_config, &xmlent_module );
210
211         if( xmlEntInScript && ! config->escapeScript ) {
212                 _fwrite( filter, "%s", data );
213
214         } else {
215                 char* escaped = ap_escape_html(filter->r->pool, data);
216                 _fwrite( filter, "%s", escaped );
217         } 
218 }
219
220 static void XMLCALL handlePI( void* userData, const XML_Char* target, const XML_Char* data) {
221         ap_filter_t* filter = (ap_filter_t*) userData;
222         _fwrite(filter, "<?%s %s?>", target, data);
223 }
224
225 static void XMLCALL handleComment( void* userData, const XML_Char* comment ) {
226         ap_filter_t* filter = (ap_filter_t*) userData;
227         _fwrite(filter, "<!-- %s -->", comment);
228 }
229
230 /* Ends an XML element */
231 static void XMLCALL endElement(void *userData, const char *name) {
232         ap_filter_t* filter = (ap_filter_t*) userData;
233         _fwrite( filter, "</%s>", name );
234         if(!strcmp(name, "script")) 
235                 xmlEntInScript = 1;
236 }
237
238 static void XMLCALL doctypeHandler( void* userData, 
239         const char* name, const char* sysid, const char* pubid, int hasinternal ) {
240
241         ap_filter_t* filter = (ap_filter_t*) userData;
242         char* s = (sysid) ? (char*) sysid : "";
243         char* p = (pubid) ? (char*) pubid : "";
244         _fwrite( filter, "<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n", name, p, s );
245 }
246
247
248 /* The handler.  Create a new parser and/or filter context where appropriate
249  * and parse the chunks of data received from the brigade
250  */
251 static int xmlEntHandler( ap_filter_t *f, apr_bucket_brigade *brigade ) {
252
253         xmlEntContext* ctx = f->ctx;
254         apr_bucket* currentBucket = NULL;
255         apr_pool_t* pool = f->r->pool;
256         const char* data;
257         apr_size_t len;
258
259         /* load the per-dir/location config */
260         xmlEntConfig* config = ap_get_module_config( 
261                         f->r->per_dir_config, &xmlent_module );
262
263         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 
264                         0, f->r, "XMLENT Config:\nContent Type = %s, "
265                         "Strip PI = %s, Strip Comments = %s, Doctype = %s", 
266                         config->contentType, 
267                         (config->stripPI) ? "yes" : "no", 
268                         (config->stripComments) ? "yes" : "no",
269                         config->doctype);
270
271         /* set the content type based on the config */
272         ap_set_content_type(f->r, config->contentType);
273
274
275         /* create the XML parser */
276         int firstrun = 0;
277         if( parser == NULL ) {
278                 firstrun = 1;
279                 parser = XML_ParserCreate("UTF-8");
280                 XML_SetUserData(parser, f);
281                 XML_SetElementHandler(parser, startElement, endElement);
282                 XML_SetCharacterDataHandler(parser, charHandler);
283                 if(!config->stripDoctype)
284                         XML_SetStartDoctypeDeclHandler( parser, doctypeHandler );
285                 if(!config->stripPI)
286                         XML_SetProcessingInstructionHandler(parser, handlePI);
287                 if(!config->stripComments)
288                         XML_SetCommentHandler(parser, handleComment);
289         }
290
291         /* create the filter context */
292         if( ctx == NULL ) {
293                 f->ctx = ctx = apr_pcalloc( pool, sizeof(*ctx));
294                 ctx->brigade = apr_brigade_create( pool, f->c->bucket_alloc );
295                 ctx->parser = parser;
296         }
297
298
299         if(firstrun) { /* we haven't started writing the data to the stream yet */
300
301                 /* go ahead and write the doctype out if we have one defined */
302                 if(config->doctype) {
303                         ap_log_rerror( APLOG_MARK, APLOG_DEBUG, 
304                                         0, f->r, "XMLENT DOCTYPE => %s", config->doctype);
305                         _fwrite(f, "%s\n", config->doctype);
306                 }
307         }
308
309
310         /* cycle through the buckets in the brigade */
311         while (!APR_BRIGADE_EMPTY(brigade)) {
312
313                 /* grab the next bucket */
314                 currentBucket = APR_BRIGADE_FIRST(brigade);
315
316                 /* clean up when we're done */
317                 if (APR_BUCKET_IS_EOS(currentBucket) || APR_BUCKET_IS_FLUSH(currentBucket)) {
318                 APR_BUCKET_REMOVE(currentBucket);
319         APR_BRIGADE_INSERT_TAIL(ctx->brigade, currentBucket);
320         ap_pass_brigade(f->next, ctx->brigade);
321                         XML_ParserFree(parser);
322                         parser = NULL;
323                         return APR_SUCCESS;
324         }
325
326                 /* read the incoming data */
327                 int s = apr_bucket_read(currentBucket, &data, &len, APR_NONBLOCK_READ);
328                 if( s != APR_SUCCESS ) {
329                         ap_log_rerror( APLOG_MARK, APLOG_ERR, 0, f->r, 
330                                         "XMLENT error reading data from filter with status %d", s);
331                         return s;
332                 }
333
334                 if (len > 0) {
335
336                         ap_log_rerror( APLOG_MARK, APLOG_DEBUG, 
337                                         0, f->r, "XMLENT read %d bytes", (int)len);
338
339                         /* push data into the XML push parser */
340                         if ( XML_Parse(ctx->parser, data, len, 0) == XML_STATUS_ERROR ) {
341
342                                 /* log and die on XML errors */
343                                 ap_log_rerror( APLOG_MARK, APLOG_ERR, 0, f->r, "XMLENT XML Parse Error: %s at line %d\n",
344                                         XML_ErrorString(XML_GetErrorCode(ctx->parser)), 
345                                         (int) XML_GetCurrentLineNumber(ctx->parser));
346
347                                 XML_ParserFree(parser);
348                                 parser = NULL;
349                                 return HTTP_INTERNAL_SERVER_ERROR; 
350                         }
351         }
352
353                 /* so a subrequest doesn't re-read this bucket */
354                 apr_bucket_delete(currentBucket); 
355         }
356
357         apr_brigade_destroy(brigade);
358         return APR_SUCCESS;     
359 }
360
361
362 /* Register the filter function as a filter for modifying the HTTP body (content) */
363 static void xmlEntRegisterHook(apr_pool_t *pool) {
364   ap_register_output_filter("XMLENT", xmlEntHandler, NULL, AP_FTYPE_CONTENT_SET);
365 }
366
367 /* Define the module data */
368 module AP_MODULE_DECLARE_DATA xmlent_module = {
369   STANDARD20_MODULE_STUFF,
370   xmlEntCreateDirConfig,        /* dir config creater */
371   NULL,                                                 /* dir merger --- default is to override */
372   NULL,                                       /* server config */
373   NULL,                    /* merge server config */
374   xmlEntCommands,          /* command apr_table_t */
375   xmlEntRegisterHook                    /* register hook */
376 };
377
378