2 #include "http_config.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"
10 #include "util_filter.h"
11 #include "opensrf/utils.h"
13 #include <sys/types.h>
17 #define MODULE_NAME "xmlent_module"
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"
27 module AP_MODULE_DECLARE_DATA xmlent_module;
31 apr_bucket_brigade* brigade; /* the bucket brigade we buffer our data into */
32 XML_Parser parser; /* our XML parser */
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 */
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;
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;
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;
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"),
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;
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;
91 /* keep for a while in case we ever need it */
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);
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;
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));
120 /* cycles through the attributes attached to an element */
121 static void printAttr( ap_filter_t* filter, const char** atts ) {
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 );
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 );
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;
146 memcpy( data, s, len );
147 char* escaped = ap_escape_html(filter->r->pool, data);
148 _fwrite( filter, escaped );
151 static void XMLCALL handlePI( void* userData, const XML_Char* target, const XML_Char* data) {
153 fprintf(stderr, "target=%s : data=%s\n", target, data);
156 ap_filter_t* filter = (ap_filter_t*) userData;
157 _fwrite(filter, "<?%s %s?>", target, data);
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 );
167 /* The handler. Create a new parser and/or filter context where appropriate
168 * and parse the chunks of data received from the brigade
170 static int xmlEntHandler( ap_filter_t *f, apr_bucket_brigade *brigade ) {
172 xmlEntContext* ctx = f->ctx;
173 apr_bucket* currentBucket = NULL;
174 apr_pool_t* pool = f->r->pool;
178 /* load the per-dir/location config */
179 xmlEntConfig* config = ap_get_module_config(
180 f->r->per_dir_config, &xmlent_module );
182 ap_log_rerror(APLOG_MARK, APLOG_DEBUG,
183 0, f->r, "XMLENT Content Type = %s", config->contentType);
185 /* set the content type based on the config */
186 ap_set_content_type(f->r, config->contentType);
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);
195 XML_SetProcessingInstructionHandler(parser, handlePI);
198 /* create the filter context */
200 f->ctx = ctx = apr_pcalloc( pool, sizeof(*ctx));
201 ctx->brigade = apr_brigade_create( pool, f->c->bucket_alloc );
202 ctx->parser = parser;
205 /* cycle through the buckets in the brigade */
206 while (!APR_BRIGADE_EMPTY(brigade)) {
208 /* grab the next bucket */
209 currentBucket = APR_BRIGADE_FIRST(brigade);
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);
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);
231 ap_log_rerror( APLOG_MARK, APLOG_DEBUG,
232 0, f->r, "XMLENT read %d bytes", (int)len);
234 /* push data into the XML push parser */
235 if ( XML_Parse(ctx->parser, data, len, 0) == XML_STATUS_ERROR ) {
237 /* log and die on XML errors */
238 ap_log_rerror( APLOG_MARK, APLOG_ERR, 0, f->r, "%s at line %d\n",
239 XML_ErrorString(XML_GetErrorCode(ctx->parser)),
240 XML_GetCurrentLineNumber(ctx->parser));
242 return HTTP_INTERNAL_SERVER_ERROR;
246 /* so a subrequest doesn't re-read this bucket */
247 apr_bucket_delete(currentBucket);
250 apr_brigade_destroy(brigade);
255 /* Register the filter function as a filter for modifying the HTTP body (content) */
256 static void xmlEntRegisterHook(apr_pool_t *pool) {
257 ap_register_output_filter("XMLENT", xmlEntHandler, NULL, AP_FTYPE_CONTENT_SET);
260 /* Define the module data */
261 module AP_MODULE_DECLARE_DATA xmlent_module = {
262 STANDARD20_MODULE_STUFF,
263 xmlEntCreateDirConfig, /* dir config creater */
264 NULL, /*xmlEntMergeDirConfig,*/ /* dir merger --- default is to override */
265 NULL, /* server config */
266 NULL, /* merge server config */
267 xmlEntCommands, /* command apr_table_t */
268 xmlEntRegisterHook /* register hook */