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