/*
Copyright (c) 2007 by Deviurg LLC http://deviurg.com
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <libxml/parser.h>
#include <libxml/xinclude.h>
#include <libxml/uri.h>
#include <libxslt/xslt.h>
#include <libxslt/transform.h>
#include <libxslt/security.h>
#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_log.h"
#include "ap_config.h"
#include "apr_buckets.h"
#include "apr_strings.h"
#include "apr_hash.h"
#include "apr_lib.h"
#include "apr_pools.h"
#include "apr_buckets.h"
#include "config.h"
#include "xslt_filter.h"
/* --------------------- Filter config --------------------- */
module xslt_filter;
typedef struct {
apr_pool_t *pool;
int enabled;
int debug;
int error_handling;
int external;
char *default_xslt;
char *xslt_root;
int xslt_root_auto;
} xslt_dir_config;
static void *
create_dir_config(apr_pool_t *p, char *dir) {
xslt_dir_config *config;
config=(xslt_dir_config*)apr_palloc(p,sizeof(xslt_dir_config));
/* XXX */
config->pool=p;
config->enabled=1;
config->debug=1;
config->external=0;
config->error_handling=0;
config->default_xslt=NULL;
config->xslt_root=NULL;
config->xslt_root_auto=0;
return(config);
}
static void *
merge_dir_config(apr_pool_t *p, void *orig, void *overr) {
xslt_dir_config *new;
xslt_dir_config *config=(xslt_dir_config *)overr;
new=(xslt_dir_config*)apr_palloc(p,sizeof(xslt_dir_config));
new->pool=p;
new->enabled=config->enabled;
new->debug=config->debug;
new->external=config->external;
new->error_handling=config->error_handling;
new->default_xslt=config->default_xslt;
new->xslt_root=config->xslt_root;
new->xslt_root_auto=config->xslt_root_auto;
return(new);
}
/* --------------------- Filter Commands --------------------- */
const char *
cmd_XSLT_Filter(cmd_parms *cmd, void *cfg,const char *name, const char *value) {
xslt_dir_config *config=(xslt_dir_config *)cfg;
if (strcasecmp(name,"debug")==0) {
if (value && strcasecmp(value,"on") == 0) {
config->debug=1;
} else {
config->debug=0;
}
} else if(strcasecmp(name,"enabled")==0) {
if (value && strcasecmp(value,"on") == 0) {
config->enabled=1;
} else {
config->enabled=0;
}
} else if(strcasecmp(name,"external")==0) {
if (value && strcasecmp(value,"on") == 0) {
config->external=1;
} else {
config->external=0;
}
} else if(strcasecmp(name,"xsltroot")==0) {
if (value && strcasecmp(value,"off") == 0) {
config->xslt_root=NULL;
config->xslt_root_auto=0;
} else if (value && strcasecmp(value,"auto") == 0) {
config->xslt_root=NULL;
config->xslt_root_auto=1;
} else {
config->xslt_root=strdup(value);
}
} else if(strcasecmp(name,"default")==0) {
if (value && strcasecmp(value,"off") == 0) {
config->default_xslt=NULL;
} else {
config->default_xslt=strdup(value);
}
} else {
fprintf(stderr,"Unknown command 'XSLT_Filter %s'\n",name);
}
return NULL;
}
static const command_rec xslt_filter_commands[] = {
AP_INIT_TAKE12("XSLT_Filter", cmd_XSLT_Filter, NULL, OR_FILEINFO, "XSLT_Filter <option> <value>"),
{NULL}
};
/* --------------------- Child Init/Exit --------------------- */
static apr_status_t
xslt_filter_child_exit(void *data) {
/* ap_log_rerror(APLOG_MARK,APLOG_ERR,0,f->r,"XSLT_Filter: child exit"); */
xsltCleanupGlobals();
xmlCleanupParser();
xmlMemoryDump();
return APR_SUCCESS;
}
static void
xslt_filter_child_init(apr_pool_t *p, server_rec *serv) {
/* ap_log_perror(APLOG_MARK,APLOG_ERR, 0, p, "XSLT_Filter: child init"); */
apr_pool_cleanup_register(p, p, xslt_filter_child_exit, xslt_filter_child_exit);
xmlInitMemory();
LIBXML_TEST_VERSION
xsltSecurityPrefsPtr sec = NULL;
xsltSetDefaultSecurityPrefs(sec);
xmlLineNumbersDefault(1);
xmlSubstituteEntitiesDefault(1);
xmlLoadExtDtdDefaultValue=1;
xmlDefaultSAXHandlerInit();
xmlDefaultSAXHandler.cdataBlock = NULL;
/* XXX Cache? */
}
/* --------------------- Filter IO --------------------- */
int xslt_filter_IO(void * context, const char * buffer, int len) {
ap_filter_t *f = (ap_filter_t*)context;
apr_bucket_brigade *brigade = (apr_bucket_brigade *)f->ctx;
char *buf = apr_palloc(f->r->pool, len);
if (!buf) return(-1);
memcpy(buf,buffer,len);
apr_bucket *b = apr_bucket_pool_create(buf, len, f->r->pool, f->c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(brigade, b);
return(len);
}
/* --------------------- Filter CTX --------------------- */
static apr_status_t
xslt_filter_ctx(ap_filter_t *f, apr_bucket_brigade *brigade) {
xslt_dir_config *config;
apr_bucket_brigade *data;
apr_bucket_brigade *result;
apr_bucket *b;
int EOS=0;
int DBG=0;
config = (xslt_dir_config *)ap_get_module_config(f->r->per_dir_config, &xslt_filter);
if (config->debug) DBG=config->debug;
/* Skip dummy work 1 */
if (config->enabled==0) {
if (DBG) ap_log_rerror(APLOG_MARK,APLOG_ERR,0,f->r,"XSLT_filter: skipped for HEAD");
ap_remove_output_filter(f);
return(ap_pass_brigade(f->next, brigade));
}
/* Skip dummy work 2 */
if (f->r->header_only) {
if (DBG) ap_log_rerror(APLOG_MARK,APLOG_ERR,0,f->r,"XSLT_filter: skipped for HEAD");
ap_remove_output_filter(f);
return(ap_pass_brigade(f->next, brigade));
}
/* First iteration? */
if (!f->ctx) f->ctx=apr_brigade_create(f->r->pool,NULL);
data=(apr_bucket_brigade*)f->ctx;
for(b = APR_BRIGADE_FIRST(brigade); b != APR_BRIGADE_SENTINEL(brigade); b = APR_BUCKET_NEXT(b)) {
if (APR_BUCKET_IS_TRANSIENT(b)) apr_bucket_setaside(b,f->r->pool);
if (APR_BUCKET_IS_EOS(b)) EOS=1;
}
APR_BRIGADE_CONCAT(data,brigade);
if (!EOS) return(APR_SUCCESS);
/* Time to make up! */
if (DBG) ap_log_rerror(APLOG_MARK,APLOG_ERR,0,f->r,"XSLT_filter: init");
xmlOutputBufferPtr output = NULL;
xmlCharEncodingHandlerPtr encoder = NULL;
xsltStylesheetPtr stylesheet = NULL;
apr_off_t length = 0;
apr_brigade_length(data,1,&length);
char *buffer = NULL;
apr_brigade_pflatten(data,&buffer,(apr_size_t *)&length,f->r->pool);
xmlDocPtr document = xmlParseMemory(buffer, length);
char **parameters = NULL;
if (!document) return(ap_pass_brigade(f->next, data));
/* XML_PARSE_NONET ? */
int net=config->external ? 0 : XML_PARSE_NONET;
xmlXIncludeProcessFlags(document,net|XML_PARSE_RECOVER|XML_PARSE_XINCLUDE);
if (config->xslt_root) {
document->URL=xmlBuildURI(config->xslt_root,NULL);
stylesheet = xsltLoadStylesheetPI(document);
}
if (config->xslt_root_auto) {
if (!stylesheet) {
document->URL=xmlBuildURI(f->r->filename,NULL);
stylesheet = xsltLoadStylesheetPI(document);
}
if (!stylesheet && config->external) {
document->URL=xmlBuildURI(
f->r->uri,
apr_psprintf(f->r->pool,
"http://%s:%d%s",
f->r->hostname,
(f->r->server->port?f->r->server->port:80),
f->r->uri
)
);
stylesheet = xsltLoadStylesheetPI(document);
}
}
if (!stylesheet && config->default_xslt) {
stylesheet = xsltParseStylesheetFile(config->default_xslt);
}
if (DBG) ap_log_rerror(APLOG_MARK,APLOG_ERR,0,f->r,"XSLT_filter: document->BASE %s",xmlNodeGetBase(document,(xmlNodePtr)document));
if (stylesheet) {
if (DBG) ap_log_rerror(APLOG_MARK,APLOG_ERR,0,f->r,"XSLT_filter: document has stylesheet %s",stylesheet->doc->URL);
} else {
/* stylesheet = xsltParseStylesheetFile(URL); */
if (DBG) ap_log_rerror(APLOG_MARK,APLOG_ERR,0,f->r,"XSLT_filter: document has no default stylesheet");
}
if (!stylesheet) return(ap_pass_brigade(f->next, data));
if (stylesheet->mediaType) {
apr_table_set(f->r->notes,"Media-Type",stylesheet->mediaTy