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