Hi Apache developers,

next is a feature that extends mod_proxy_html to rewrite URLs in CSS documents too. This is done by applying the configured regex and string replacements for the content of HTML tags also on whole CSS documents, i.e. documents with Content-Type: text/css.

The attached patch is based on httpd trunk, rev. 1579365. I've also filed this in the ASF bugzilla as issue #56284.

Regards,
Micha
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, &current_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.
      */

Reply via email to