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