added content-type back into gateway
[Evergreen.git] / Open-ILS / src / apachemods / mod_xmlbuilder.c
1 #include "mod_xmlbuilder.h"
2
3 char* __xmlBuilderDynamicLocale = NULL;
4 request_rec* currentRec = NULL;
5
6
7 /* set the base DTD directory */
8 static const char* xmlBuilderSetBaseDir(cmd_parms *params, void *cfg, const char *arg) {
9         xmlBuilderConfig* config = ap_get_module_config(
10                 params->server->module_config, &xmlbuilder_module );
11         config->baseDir = (char*) arg;
12         return NULL;
13 }
14
15 static const char* xmlBuilderSetDefaultLocale(
16                                          cmd_parms* params, void* cfg, const char* arg ) {
17         xmlBuilderConfig* config = ap_get_module_config(
18                 params->server->module_config, &xmlbuilder_module );
19         config->defaultLocale = (char*) arg;
20         return NULL;
21 }
22
23 static const char* xmlBuilderSetDefaultDtd(
24                                          cmd_parms* params, void* cfg, const char* arg ) {
25         xmlBuilderConfig* config = ap_get_module_config(
26                 params->server->module_config, &xmlbuilder_module );
27         config->defaultDtd = (char*) arg;
28         if( config->defaultDtd ) {
29                 if(!strcmp(config->defaultDtd,"NULL"))
30                         config->defaultDtd = NULL;
31         }
32         return NULL;
33 }
34
35
36 static const char* xmlBuilderSetLocaleParam(
37                                          cmd_parms* params, void* cfg, const char* arg ) {
38         xmlBuilderConfig* config = ap_get_module_config(
39                 params->server->module_config, &xmlbuilder_module );
40         config->localeParam = (char*) arg;
41         return NULL;
42 }
43
44
45 static const char* xmlBuilderSetPostXSL(
46                                          cmd_parms* params, void* cfg, const char* arg ) {
47         xmlBuilderConfig* config = ap_get_module_config(
48                 params->server->module_config, &xmlbuilder_module );
49         config->postXSL = xsltParseStylesheetFile((xmlChar*) arg);
50         if( config->postXSL == NULL ) 
51                 apacheDebug("Unable to parse postXSL stylesheet: %s.  No postXSL will be performed", arg);      
52         return NULL;
53 }
54
55 static const char* xmlBuilderSetContentType(
56                                          cmd_parms* params, void* cfg, const char* arg ) {
57         xmlBuilderConfig* config = ap_get_module_config(
58                 params->server->module_config, &xmlbuilder_module );
59         config->contentType = (char*) arg;
60         return NULL;
61 }
62
63 // ACCESS_CONF - OR_ALL ?
64 static const command_rec xmlBuilderCommands[] = {
65         AP_INIT_TAKE1( MODXMLB_CONFIG_LOCALE, 
66                         xmlBuilderSetDefaultLocale, NULL, RSRC_CONF, "Default Locale"),
67         AP_INIT_TAKE1( MODXMLB_CONFIG_BASE_DIR, 
68                         xmlBuilderSetBaseDir, NULL, RSRC_CONF, "Base Directory"),
69         AP_INIT_TAKE1( MODXMLB_CONFIG_POST_XSL, 
70                         xmlBuilderSetPostXSL, NULL, RSRC_CONF, "Post XSL"),
71         AP_INIT_TAKE1( MODXMLB_CONFIG_DEFAULT_DTD, 
72                         xmlBuilderSetDefaultDtd, NULL, RSRC_CONF, "Default DTD"),
73         AP_INIT_TAKE1( MODXMLB_CONFIG_LOCALE_PARAM,
74                         xmlBuilderSetLocaleParam, NULL, RSRC_CONF, "Locale URL param name"),
75         AP_INIT_TAKE1( MODXMLB_CONFIG_CONTENT_TYPE,
76                         xmlBuilderSetContentType, NULL, RSRC_CONF, "Content Type"),
77         {NULL}
78 };
79
80 static void* xmlBuilderCreateConfig( apr_pool_t* p, server_rec* s ) {
81         xmlBuilderConfig* config = 
82                 (xmlBuilderConfig*) apr_palloc( p, sizeof(xmlBuilderConfig) );
83         config->baseDir                 = MODXMLB_DEFAULT_BASE_DIR;
84         config->defaultLocale   = MODXMLB_DEFAULT_LOCALE;
85         config->defaultDtd              = NULL;
86         config->postXSL                 = NULL;
87         config->contentType             = NULL;
88         config->localeParam             = MODXMLB_DEFAULT_LOCALE_PARAM;
89         return (void*) config;
90 }
91
92
93 /* Child Init handler  ----------------------------------------------------------- */
94 static void xmlBuilderChildInit( apr_pool_t *p, server_rec *s ) {
95 }
96
97 static int xmlBuilderHandler( request_rec* r ) {
98
99         if( strcmp(r->handler, MODULE_NAME ) ) return DECLINED;
100         currentRec = r;
101
102         xmlBuilderConfig* config = ap_get_module_config( 
103                         r->server->module_config, &xmlbuilder_module );
104
105         /*
106         xmlBuilderConfig* config = ap_get_module_config( 
107                         r->per_dir_config, &xmlbuilder_module );
108                         */
109
110         if(config == NULL) {
111                 ap_log_rerror( APLOG_MARK, APLOG_ERR, 0, currentRec,
112                                 "config is nulll...");
113                 return HTTP_INTERNAL_SERVER_ERROR; 
114         }
115         
116         r->allowed |= (AP_METHOD_BIT << M_GET);
117         r->allowed |= (AP_METHOD_BIT << M_POST);
118         char* ct = config->contentType;
119         if(!config->contentType) ct = "text/html; charset=utf-8";
120         ap_set_content_type(r, ct);
121
122         //ap_table_set(r->headers_out, "Cache-Control", "max-age=15552000");
123
124         char* dates = apr_pcalloc(r->pool, MAX_STRING_LEN);
125         apr_rfc822_date(dates, apr_time_now() + 604800000000); /* cache for one week */
126         ap_table_set(r->headers_out, "Expires", dates);
127
128         apr_rfc822_date(dates, apr_time_now()); /* cache for one week */
129         ap_table_set(r->headers_out, "Date", dates);
130
131
132         /*
133         char expire_hdr[APR_RFC822_DATE_LEN];
134          apr_rfc822_date(expire_hdr, (apr_time_t)(time(NULL) + 15552000));
135         ap_table_set(r->headers_out, "Expires", expire_hdr);
136          */
137
138         string_array* params = apacheParseParms(r);
139         char* locale = apacheGetFirstParamValue(params, config->localeParam);
140         if(locale) __xmlBuilderDynamicLocale = locale;
141         char* XMLFile = r->filename;
142
143         xmlDocPtr doc = xmlBuilderProcessFile( XMLFile, config );
144         if(!doc) return apacheError( "Unable to parse XML file %s", XMLFile );
145
146         /* apply the post XSL */
147         if(config->postXSL) {
148                 xmlDocPtr newdoc;
149                 newdoc = xsltApplyStylesheet(config->postXSL, doc, NULL );
150
151                 if(newdoc == NULL) {
152                         apacheDebug("Error applying postXSL... skipping.");
153                 } else {
154                         xmlFreeDoc(doc);
155                         doc = newdoc;
156                 }
157         }
158
159         char* docXML = xmlDocToString( doc, 1 );
160         //apacheDebug("DOC:\n%s\n%s", docXML);
161         ap_rputs(docXML, r);
162         free(docXML);
163         xmlFreeDoc( doc );
164         doc = NULL;
165         //xmlCleanupCharEncodingHandlers();
166         //xmlCleanupParser();
167
168         return OK;
169 }
170
171
172 /* frees the collected DTD's */
173 static void __xmlBuilderFreeDtdHash( char* key, void* item ) {
174         if(!item) return;
175         xmlFreeDtd( item );
176 }
177
178
179 xmlDocPtr xmlBuilderProcessFile( char* filename, xmlBuilderConfig* config ) {
180         if(!filename) { 
181                 apacheError( "No XML file provided" ); return NULL; }
182
183         xmlBuilderContext context;
184         context.config          = config;
185         context.doc                     = xmlNewDoc( BAD_CAST "1.0" );
186         context.dtdHash = osrfNewHash();
187         context.entHash = osrfNewHash();
188         context.nodeList        = osrfNewList();
189         context.xmlError        = 0;
190         context.xmlFile = filename;
191         context.dtdHash->freeItem = &__xmlBuilderFreeDtdHash;
192
193                 
194         /*
195         ap_log_rerror( APLOG_MARK, APLOG_DEBUG, 0, currentRec,
196                         "xmlBuilderProcessFile() Options: "
197                         "XMLBuilderDefaultLocale : %s |  XMLBuilderBaseDir : %s | "
198          "XMLBuilderDefaultDTD : %s | XMLBuilderLocaleParam : %s",
199                         config->defaultLocale, config->baseDir, config->defaultDtd, config->localeParam );
200                         */
201
202         /* pre-parse the default dtd if defined */
203         if( config->defaultDtd ) 
204                 xmlBuilderAddDtd( config->defaultDtd, &context );
205
206         xmlParserCtxtPtr parserCtx;
207
208         parserCtx = xmlCreatePushParserCtxt(xmlBuilderSaxHandler, &context, "", 0, NULL);
209         xmlCtxtReadFile( parserCtx, filename, NULL, XML_PARSE_RECOVER );
210
211         xmlFreeParserCtxt( parserCtx );
212         osrfListFree(context.nodeList);
213         osrfHashFree(context.entHash);
214         osrfHashFree(context.dtdHash);
215         return context.doc;
216 }
217
218
219 void xmlBuilderStartElement( void* context, const xmlChar *name, const xmlChar **atts ) {
220         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
221
222         xmlNodePtr node = NULL;
223
224         /* process xincludes as a sub-doc */
225         if( !strcmp( name, "xi:include" ) ) { 
226
227                 char* href = strdup(xmlSaxAttr( atts, "href" ));
228                 if(href) {
229
230                         /* find the relative path for the xinclude */
231                         if(href[0] != '/') {
232                                 int len = strlen(ctx->xmlFile) + strlen(href) + 1;
233                                 char buf[len];
234                                 bzero(buf, len);
235                                 strcpy( buf, ctx->xmlFile );
236                                 int i;
237                                 for( i = strlen(buf); i != 0; i-- ) {
238                                         if( buf[i] == '/' ) break;
239                                         buf[i] = '\0';
240                                 }
241                                 strcat( buf, href );
242                                 free(href);
243                                 href = strdup(buf);
244                         }
245
246
247                         xmlDocPtr subDoc = xmlBuilderProcessFile( href, ctx->config );
248                         node = xmlDocGetRootElement( subDoc );
249                 }
250
251                 if(!node) {
252                         apacheError("Unable to parse xinclude: %s", href );
253                         free(href);
254                         return;
255                 }
256                 free(href);
257
258         } else {
259                 node = xmlNewNode(NULL, name);
260                 xmlBuilderAddAtts( ctx, node, atts );
261         }
262
263
264         xmlNodePtr parent = osrfListGetIndex( 
265                         ctx->nodeList, ctx->nodeList->size - 1 );
266
267         if( parent ) xmlAddChild( parent, node );
268         else xmlDocSetRootElement(ctx->doc, node);
269         
270         osrfListPush( ctx->nodeList, node );
271 }
272
273
274 void xmlBuilderAddAtts( xmlBuilderContext* ctx, xmlNodePtr node, const xmlChar** atts ) {
275         if(!(ctx && node && atts)) return;
276         int i;
277
278         for(i = 0; (atts[i] != NULL); i++) {
279
280                 if(atts[i+1]) {
281
282                         const xmlChar* name = atts[i];
283                         const xmlChar* prop = atts[i+1];
284                         int nl = strlen(prop);
285                         char* _prop = NULL;
286
287                         if( prop[0] == '&' && prop[nl-1] == ';' ) { /* replace the entity if we are one */
288                                 char buf[nl+1];
289                                 bzero(buf, nl+1);
290                                 strncat(buf, prop + 1, nl - 2);
291                                 xmlEntityPtr ent = osrfHashGet( ctx->entHash, buf );
292                                 if(ent && ent->content) _prop = ent->content;
293                         } else { _prop = (char*) prop; }
294
295                         xmlSetProp( node, name, _prop );
296                         i++;
297                 }
298         }
299 }
300
301 void xmlBuilderEndElement( void* context, const xmlChar* name ) {
302         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
303         osrfListPop( ctx->nodeList );
304 }
305
306
307 void xmlBuilderHandleCharacter(void* context, const xmlChar *ch, int len) {
308         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
309         xmlNodePtr node = osrfListGetIndex( 
310                         ctx->nodeList, ctx->nodeList->size - 1 );
311
312         if(node) {
313                 xmlNodePtr txt = xmlNewTextLen(ch, len);
314                 xmlAddChild( node, txt );
315         }
316
317 }
318
319
320 void xmlBuilderParseError( void* context, const char* msg, ... ) {
321         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
322         VA_LIST_TO_STRING(msg);
323         apacheDebug( "Parser Error Occurred: %s", VA_BUF);
324         ctx->xmlError = 1;
325 }
326
327
328 xmlEntityPtr xmlBuilderGetEntity( void* context, const xmlChar* name ) {
329         xmlBuilderContext* ctx = (xmlBuilderContext*) context;
330         xmlEntityPtr ent = osrfHashGet( ctx->entHash, name );
331         return ent;
332 }
333
334
335 void xmlBuilderExtSubset( void* blob, 
336                 const xmlChar* name, const xmlChar* extId, const xmlChar* sysId ) {
337
338         xmlBuilderContext* context = (xmlBuilderContext*) blob;
339         if( context->config->defaultDtd ) {
340                 apacheDebug("Ignoring DTD [%s] because default DTD is set...", sysId);
341                 return; 
342         }
343
344         xmlBuilderAddDtd( sysId, context );
345 }
346
347
348 void xmlBuilderProcInstruction( 
349                         void* blob, const xmlChar* name, const xmlChar* data ) {
350         xmlBuilderContext* ctx = (xmlBuilderContext*) blob;
351         //xmlNodePtr node = xmlNewDocPI( ctx->doc, name, data );
352         xmlNodePtr pi = xmlNewDocPI( ctx->doc, name, data );
353         xmlAddChild( (xmlNodePtr) ctx->doc, pi );
354 }
355
356
357
358 void xmlBuilderAddDtd( const char* sysId, xmlBuilderContext* context ) {
359
360         if(!sysId) return;
361         if( osrfHashGet( context->dtdHash, sysId ) ) return; /* already parsed this hash */
362
363         //apacheDebug("Adding new DTD file to the entity hash: %s", sysId);
364
365         /* use the dynamic locale if defined... default locale instead */
366         char* locale;
367         if(__xmlBuilderDynamicLocale) locale = __xmlBuilderDynamicLocale;
368         else locale = context->config->defaultLocale;
369
370         /* determine the path to the DTD file and load it */
371         int len = strlen(context->config->baseDir) + strlen(locale) + strlen(sysId) + 4;
372         char buf[len]; bzero(buf,len);
373         snprintf( buf, len, "%s/%s/%s", context->config->baseDir, locale, sysId );
374
375         xmlDtdPtr dtd = xmlParseDTD(NULL, buf);
376         if(!dtd) return;
377
378         /* cycle through entities and push them into the entity hash */
379         xmlNodePtr node = dtd->children;
380         while( node ) {
381                 if( node->type == XML_ENTITY_DECL ) { /* shove the entities into the hash */
382                         xmlEntityPtr ent = (xmlEntityPtr) node;
383                         osrfHashSet( context->entHash, ent, (char*) ent->name );
384                 }
385                 node = node->next;
386         }
387
388         /* cache the DTD so we can free it later */
389         osrfHashSet( context->dtdHash, dtd, sysId );
390 }
391
392
393 /* ------------------------------------------------------------------------ */
394
395 /* register callbacks */
396 static void xmlBuilderRegisterHooks (apr_pool_t *p) {
397         ap_hook_handler(xmlBuilderHandler, NULL, NULL, APR_HOOK_MIDDLE);
398         ap_hook_child_init(xmlBuilderChildInit,NULL,NULL,APR_HOOK_MIDDLE);
399 }
400
401
402 /* finally, flesh the module */
403 module AP_MODULE_DECLARE_DATA xmlbuilder_module = {
404         STANDARD20_MODULE_STUFF,
405         NULL,
406         NULL,
407         xmlBuilderCreateConfig,
408         NULL,
409         xmlBuilderCommands,
410         xmlBuilderRegisterHooks,
411 };
412
413
414
415
416