Index: modules/filters/mod_proxy_html.c =================================================================== --- modules/filters/mod_proxy_html.c (Revision 1579365) +++ modules/filters/mod_proxy_html.c (Arbeitskopie) @@ -65,6 +65,8 @@ #define M_INTERPOLATE_TO 0x100 #define M_INTERPOLATE_FROM 0x200 +#define M_PROXY_CSS_MIN_BUFFER_SIZE 4096 + typedef struct { const char *val; } tattr; @@ -103,6 +105,7 @@ proxy_html_conf *cfg; htmlParserCtxtPtr parser; apr_bucket_brigade *bb; + apr_bucket_brigade *bbsave; char *buf; size_t offset; size_t avail; @@ -953,6 +956,137 @@ return APR_SUCCESS; } +static int proxy_css_filter(ap_filter_t *f, apr_bucket_brigade *bb) +{ + apr_status_t rv; + proxy_html_conf* cfg = ap_get_module_config(f->r->per_dir_config, &proxy_html_module); + saxctxt *fctx = f->ctx; + if (fctx == NULL) { + int skip_filter = 0; + // Skip this filter if this is no proxy request + if ( ! f->r->proxyreq ) { + skip_filter = 1; + // init filter only if content type is set + } else if ( ! f->r->content_type ) { + skip_filter = 1; + // init filter only for MIME type text/css + } else if ( strncmp(f->r->content_type, "text/css", 8) != 0 ) { + skip_filter = 1; + } + + if (skip_filter) { + ap_filter_t* fnext = f->next; + ap_remove_output_filter(f); + return ap_pass_brigade(fnext, bb); + } + + // The CSS filter changes the output length + apr_table_unset(f->r->headers_out, "Content-Length") ; + + // Initialize CSS output filter + fctx = f->ctx = apr_pcalloc(f->r->pool, sizeof(saxctxt)); + fctx->f = f; + fctx->cfg = ap_get_module_config(f->r->per_dir_config, &proxy_html_module); + fctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); + fctx->bbsave = apr_brigade_create(f->r->pool, f->c->bucket_alloc); + + // Add rules for variable interpolation etc. + if (cfg->interp) fixup_rules(fctx); + else fctx->map = fctx->cfg->map; + } + + int process_bb_out = 0; + apr_bucket* current_bucket; + apr_bucket* next_bucket = NULL; + for (current_bucket = APR_BRIGADE_FIRST(bb); + current_bucket != APR_BRIGADE_SENTINEL(bb); + current_bucket = (next_bucket ? next_bucket : APR_BUCKET_NEXT(current_bucket))) { + char* out_data = NULL; + apr_size_t out_data_length = 0; + next_bucket = NULL; + if (APR_BUCKET_IS_EOS(current_bucket)) { + process_bb_out = 1; + } else if (APR_BUCKET_IS_METADATA(current_bucket)) { + continue; + } else { + const char* in_data; + apr_size_t in_data_length; + rv = apr_bucket_read(current_bucket, &in_data, &in_data_length, APR_BLOCK_READ); + if (rv != APR_SUCCESS) return rv; + + // skip empty data bucket + if (in_data_length == 0) continue; + + // scan for newline characters + apr_size_t newline_offset = 0; + const char *le_r = memchr(in_data, '\r', in_data_length); + const char *le_n = memchr(in_data, '\n', in_data_length); + if (le_r) newline_offset = (le_r - in_data) + 1; + if (le_n > le_r) newline_offset = (le_n - in_data) + 1; + + // Bucket without newline character: simply append to bbsave and process it later + if (newline_offset == 0) { + next_bucket = APR_BUCKET_NEXT(current_bucket); + APR_BUCKET_REMOVE(current_bucket); + apr_bucket_setaside(current_bucket, f->r->pool); + APR_BRIGADE_INSERT_TAIL(fctx->bbsave, current_bucket); + continue; + } + + // If newline is in the middle of the bucket: split the bucket + if (newline_offset < in_data_length) { + apr_bucket_split(current_bucket, newline_offset); + } + + // Append bucket (with data up to and including the newline character) + // to fctx->bbsave and trigger its processing + next_bucket = APR_BUCKET_NEXT(current_bucket); + APR_BUCKET_REMOVE(current_bucket); + APR_BRIGADE_INSERT_TAIL(fctx->bbsave, current_bucket); + process_bb_out = 1; + } + + if (process_bb_out == 0) continue; + + // Check buffer size requirements + apr_off_t needed_buffer_length; + apr_brigade_length(fctx->bbsave, 1, &needed_buffer_length); + if (needed_buffer_length > 0) { + // start with an empty buffer and ensure the needed buffer length is available + fctx->offset = 0; + preserve(fctx, needed_buffer_length + 1); + + // Dump bucket brigade bbsave into the output buffer + apr_size_t current_length = fctx->avail; + rv = apr_brigade_flatten(fctx->bbsave, fctx->buf, ¤t_length); + if (rv != APR_SUCCESS) return rv; + fctx->offset = current_length; + + // Terminate buffer with NULL byte, needed for regexp matching + const char zero_byte = 0; + pappend(fctx, &zero_byte, 1); + + // cleanup the saved brigade and mark it as processed + apr_brigade_cleanup(fctx->bbsave); + process_bb_out = 0; + + // DEBUG OUTPUT + const char* buf_log = apr_pstrndup(f->r->pool, fctx->buf, fctx->offset); + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, f->r, "Processing chunk: [%s]", buf_log); + + // Replace matching regexps + dump_content(fctx); + } + if (APR_BUCKET_IS_EOS(current_bucket)) { + APR_BUCKET_REMOVE(current_bucket); + APR_BRIGADE_INSERT_TAIL(fctx->bb, current_bucket); + break; + } + } + apr_brigade_cleanup(bb); + return ap_pass_brigade(f->next, fctx->bb); +} + static void *proxy_html_config(apr_pool_t *pool, char *x) { proxy_html_conf *ret = apr_pcalloc(pool, sizeof(proxy_html_conf)); @@ -1257,6 +1391,7 @@ if (xml2enc_filter) xml2enc_filter(r, NULL, ENCIO_INPUT_CHECKS); ap_add_output_filter("proxy-html", NULL, r, r->connection); + ap_add_output_filter("proxy-css", NULL, r, r->connection); } } static void proxy_html_hooks(apr_pool_t *p) @@ -1265,6 +1400,9 @@ ap_register_output_filter_protocol("proxy-html", proxy_html_filter, NULL, AP_FTYPE_RESOURCE, AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH); + ap_register_output_filter_protocol("proxy-css", proxy_css_filter, + NULL, AP_FTYPE_RESOURCE, + AP_FILTER_PROTO_CHANGE|AP_FILTER_PROTO_CHANGE_LENGTH); /* move this to pre_config so old_expr is available to interpret * old-style conditions on URL maps. */