Here's a 2.2.x backport of Yann's patch with a directive, MergeTrailers,
added in to opt-in to the old behavior. By default, it doesn't merge the
trailers. I'm working on adding the capability to mod_log_config to log the
trailers, but I figured I'd post this patch first to get some review.

 - Ed


On Tue, May 6, 2014 at 7:45 PM, Eric Covener <cove...@gmail.com> wrote:

> On Tue, May 6, 2014 at 6:44 PM, Yann Ylavic <ylavic....@gmail.com> wrote:
> > On Tue, May 6, 2014 at 1:41 PM, Eric Covener <cove...@gmail.com> wrote:
> >> I'd really like to start chucking trailers, but the scale of these
> >> patches really frightens me for 2.4 and 2.2 and accompanying what will
> >> likely be a security roll-up.
> >
> > Understood.
> >
> >>
> >> Is anyone sitting on a more tactical version of the fix?
> >
> > Well, maybe I should stop proposing something here but maybe the
> > attached patch can help.
> > I think it's a "tactical" version of the previous one, in that it does
> > *not* touch ap_get_mime_headers*() (nor ap_rgetline*()), but
> > saves/restores the data (request's fields, notes) modified by the
> > function when called from ap_http_filter().
> >
> > It could be a way for 2.2.x, but I don't think it's enough for trunk
> > or even 2.4.x, because this implementation still breaks non-blocking
> > mode (hence event), which is (also) why the core functions were
> > modified in the previous patch.
>
> I think I could be on board for that plan. Any others?
>
> >
> > This patch (still) does not propose to merge splitted trailers into
> > headers (for those broken by the change), but when (and why) would we
> > do that?
>
> For 2.2, I would personally want to have the ability to restore the
> old merging behavior before any release.   Joe drew his line there as
> well.
>
> But I think Ed Lu is lurking and can take that last bit on.
>
> --
> Eric Covener
> cove...@gmail.com
>
Index: include/http_core.h
===================================================================
--- include/http_core.h	(revision 1593540)
+++ include/http_core.h	(working copy)
@@ -613,6 +613,10 @@
 #define AP_TRACE_ENABLE    1
 #define AP_TRACE_EXTENDED  2
     int trace_enable;
+#define AP_MERGE_TRAILERS_UNSET    0
+#define AP_MERGE_TRAILERS_ENABLE   1
+#define AP_MERGE_TRAILERS_DISABLE  2
+    int merge_trailers;
 
 } core_server_config;
 
Index: include/httpd.h
===================================================================
--- include/httpd.h	(revision 1593540)
+++ include/httpd.h	(working copy)
@@ -1006,6 +1006,11 @@
  * record to improve 64bit alignment the next time we need to break
  * binary compatibility for some other reason.
  */
+
+    /** MIME trailer environment from the request */
+    apr_table_t *trailers_in;
+    /** MIME trailer environment from the response */
+    apr_table_t *trailers_out;
 };
 
 /**
Index: modules/http/http_filters.c
===================================================================
--- modules/http/http_filters.c	(revision 1593540)
+++ modules/http/http_filters.c	(working copy)
@@ -215,6 +215,7 @@
                             ap_input_mode_t mode, apr_read_type_e block,
                             apr_off_t readbytes)
 {
+    core_server_config *conf;
     apr_bucket *e;
     http_ctx_t *ctx = f->ctx;
     apr_status_t rv;
@@ -222,6 +223,9 @@
     int http_error = HTTP_REQUEST_ENTITY_TOO_LARGE;
     apr_bucket_brigade *bb;
 
+    conf = (core_server_config *)
+        ap_get_module_config(f->r->server->module_config, &core_module);
+
     /* just get out of the way of things we don't want. */
     if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
         return ap_get_brigade(f->next, b, mode, block, readbytes);
@@ -403,13 +407,50 @@
             }
 
             if (!ctx->remaining) {
-                /* Handle trailers by calling ap_get_mime_headers again! */
+                int saved_status = f->r->status;
+                apr_table_t *saved_headers_in = f->r->headers_in;
+                const char *saved_error_notes = apr_table_get(f->r->notes,
+                                                              "error-notes");
+
+                /* By default, don't merge in the trailers
+                 * (treat UNSET as DISABLE) */
+                if (conf->merge_trailers != AP_MERGE_TRAILERS_ENABLE) {
+                    f->r->status = HTTP_OK;
+                    f->r->headers_in = f->r->trailers_in;
+                    apr_table_clear(f->r->headers_in);
+                }
+
                 ctx->state = BODY_NONE;
                 ap_get_mime_headers(f->r);
-                e = apr_bucket_eos_create(f->c->bucket_alloc);
-                APR_BRIGADE_INSERT_TAIL(b, e);
-                ctx->eos_sent = 1;
-                return APR_SUCCESS;
+                if (f->r->status == HTTP_OK) {
+                    e = apr_bucket_eos_create(f->c->bucket_alloc);
+                    APR_BRIGADE_INSERT_TAIL(b, e);
+                    ctx->eos_sent = 1;
+                    rv = APR_SUCCESS;
+                }
+                else {
+                    const char *error_notes = apr_table_get(f->r->notes,
+                                                            "error-notes");
+                    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, f->r, 
+                                  "Error while reading HTTP trailer: %i%s%s",
+                                  f->r->status, error_notes ? ": " : "",
+                                  error_notes ? error_notes : "");
+                    rv = APR_EINVAL;
+                }
+
+                if (conf->merge_trailers != AP_MERGE_TRAILERS_ENABLE) {
+                    f->r->status = saved_status;
+                    f->r->headers_in = saved_headers_in;
+                    if (saved_error_notes) {
+                        apr_table_setn(f->r->notes, "error-notes",
+                                       saved_error_notes);
+                    }
+                    else {
+                        apr_table_unset(f->r->notes, "error-notes");
+                    }
+                }
+
+                return rv;
             }
         }
     }
Index: modules/http/http_request.c
===================================================================
--- modules/http/http_request.c	(revision 1593540)
+++ modules/http/http_request.c	(working copy)
@@ -384,8 +384,10 @@
     new->main            = r->main;
 
     new->headers_in      = r->headers_in;
+    new->trailers_in     = r->trailers_in;
     new->headers_out     = apr_table_make(r->pool, 12);
     new->err_headers_out = r->err_headers_out;
+    new->trailers_out    = r->trailers_out;
     new->subprocess_env  = rename_original_env(r->pool, r->subprocess_env);
     new->notes           = apr_table_make(r->pool, 5);
 
@@ -495,6 +497,8 @@
                                        r->headers_out);
     r->err_headers_out = apr_table_overlay(r->pool, rr->err_headers_out,
                                            r->err_headers_out);
+    r->err_headers_out = apr_table_overlay(r->pool, rr->trailers_out,
+                                           r->trailers_out);
     r->subprocess_env = apr_table_overlay(r->pool, rr->subprocess_env,
                                           r->subprocess_env);
 
Index: modules/proxy/mod_proxy_http.c
===================================================================
--- modules/proxy/mod_proxy_http.c	(revision 1593540)
+++ modules/proxy/mod_proxy_http.c	(working copy)
@@ -1229,6 +1229,7 @@
     psc = (proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
 
     r->headers_out = apr_table_make(r->pool, 20);
+    r->trailers_out = apr_table_make(r->pool, 5);
     *pread_len = 0;
 
     /*
@@ -1355,6 +1356,14 @@
 #define AP_MAX_INTERIM_RESPONSES 10
 #endif
 
+static int add_trailers(void *data, const char *key, const char *val)
+{
+    if (val) {
+        apr_table_add((apr_table_t*)data, key, val);
+    }
+    return 1;
+}
+
 static
 apr_status_t ap_proxy_http_process_response(apr_pool_t * p, request_rec *r,
                                             proxy_conn_rec *backend,
@@ -1809,6 +1818,12 @@
                     /* next time try a non-blocking read */
                     mode = APR_NONBLOCK_READ;
 
+                    if (!apr_is_empty_table(backend->r->trailers_in)) {
+                        apr_table_do(add_trailers, r->trailers_out,
+                                backend->r->trailers_in, NULL);
+                        apr_table_clear(backend->r->trailers_in);
+                    }
+
                     apr_brigade_length(bb, 0, &readbytes);
                     backend->worker->s->read += readbytes;
 #if DEBUGGING
Index: server/core.c
===================================================================
--- server/core.c	(revision 1593540)
+++ server/core.c	(working copy)
@@ -542,6 +542,10 @@
                          ? virt->trace_enable
                          : base->trace_enable;
 
+    conf->merge_trailers = (virt->trace_enable != AP_MERGE_TRAILERS_UNSET)
+                           ? virt->merge_trailers
+                           : base->merge_trailers;
+
     return conf;
 }
 
@@ -3295,6 +3299,16 @@
     return NULL;
 }
 
+static const char *set_merge_trailers(cmd_parms *cmd, void *dummy, int arg)
+{
+    core_server_config *conf = ap_get_module_config(cmd->server->module_config,
+                                                    &core_module);
+    conf->merge_trailers = (arg ? AP_MERGE_TRAILERS_ENABLE :
+            AP_MERGE_TRAILERS_DISABLE);
+
+    return NULL;
+}
+
 /* Note --- ErrorDocument will now work from .htaccess files.
  * The AllowOverride of Fileinfo allows webmasters to turn it off
  */
@@ -3530,6 +3544,8 @@
 #endif
 AP_INIT_TAKE1("TraceEnable", set_trace_enable, NULL, RSRC_CONF,
               "'on' (default), 'off' or 'extended' to trace request body content"),
+AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF,
+              "merge request trailers into request headers or not"),
 #ifdef SUEXEC_BIN
 AP_INIT_FLAG("Suexec", unixd_set_suexec, NULL, RSRC_CONF,
              "Enable or disable suEXEC support"),
@@ -3632,7 +3648,6 @@
 
 static int do_nothing(request_rec *r) { return OK; }
 
-
 static int core_override_type(request_rec *r)
 {
     core_dir_config *conf =
Index: server/protocol.c
===================================================================
--- server/protocol.c	(revision 1593540)
+++ server/protocol.c	(working copy)
@@ -887,9 +887,11 @@
     r->allowed_methods = ap_make_method_list(p, 2);
 
     r->headers_in      = apr_table_make(r->pool, 25);
+    r->trailers_in     = apr_table_make(r->pool, 5);
     r->subprocess_env  = apr_table_make(r->pool, 25);
     r->headers_out     = apr_table_make(r->pool, 12);
     r->err_headers_out = apr_table_make(r->pool, 5);
+    r->trailers_out    = apr_table_make(r->pool, 5);
     r->notes           = apr_table_make(r->pool, 5);
 
     r->request_config  = ap_create_request_config(r->pool);
@@ -1141,7 +1143,8 @@
 
     rnew->status          = HTTP_OK;
 
-    rnew->headers_in = apr_table_copy(rnew->pool, r->headers_in);
+    rnew->headers_in      = apr_table_copy(rnew->pool, r->headers_in);
+    rnew->trailers_in     = apr_table_copy(rnew->pool, r->trailers_in);
 
     /* did the original request have a body?  (e.g. POST w/SSI tags)
      * if so, make sure the subrequest doesn't inherit body headers
@@ -1153,6 +1156,7 @@
     rnew->subprocess_env  = apr_table_copy(rnew->pool, r->subprocess_env);
     rnew->headers_out     = apr_table_make(rnew->pool, 5);
     rnew->err_headers_out = apr_table_make(rnew->pool, 5);
+    rnew->trailers_out    = apr_table_make(rnew->pool, 5);
     rnew->notes           = apr_table_make(rnew->pool, 5);
 
     rnew->expecting_100   = r->expecting_100;

Reply via email to