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