Responses to HEAD requests must not have a message body (even though they have
authorbenno <benno@openbsd.org>
Wed, 24 Mar 2021 20:59:53 +0000 (20:59 +0000)
committerbenno <benno@openbsd.org>
Wed, 24 Mar 2021 20:59:53 +0000 (20:59 +0000)
a Content-Length header).  HTTP RFC 7231 section 4.3.2.
found by niklas@, claudio@ agrees.

usr.sbin/relayd/http.h
usr.sbin/relayd/relay.c
usr.sbin/relayd/relay_http.c

index e768816..b9784ad 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: http.h,v 1.11 2019/05/08 23:22:19 reyk Exp $  */
+/*     $OpenBSD: http.h,v 1.12 2021/03/24 20:59:53 benno Exp $ */
 
 /*
  * Copyright (c) 2012 - 2015 Reyk Floeter <reyk@openbsd.org>
@@ -19,6 +19,8 @@
 #ifndef HTTP_H
 #define HTTP_H
 
+#include <sys/queue.h>
+
 #define HTTP_PORT      80
 #define HTTPS_PORT     443
 
@@ -251,4 +253,13 @@ struct http_descriptor {
        struct kvtree            http_headers;
 };
 
+struct http_method_node {
+       enum httpmethod                 hmn_method;
+       SIMPLEQ_ENTRY(http_method_node) hmn_entry;
+};
+
+struct http_session {
+       SIMPLEQ_HEAD(, http_method_node) hs_methods;
+};
+
 #endif /* HTTP_H */
index 02324f5..da4a1aa 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: relay.c,v 1.253 2021/01/27 20:33:05 eric Exp $        */
+/*     $OpenBSD: relay.c,v 1.254 2021/03/24 20:59:53 benno Exp $       */
 
 /*
  * Copyright (c) 2006 - 2014 Reyk Floeter <reyk@openbsd.org>
@@ -800,7 +800,7 @@ relay_input(struct rsession *con)
 
        switch (rlay->rl_proto->type) {
        case RELAY_PROTO_HTTP:
-               if (relay_httpdesc_init(&con->se_in) == -1) {
+               if (relay_http_priv_init(con) == -1) {
                        relay_close(con,
                            "failed to allocate http descriptor", 1);
                        return;
index 69b4869..29e401e 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: relay_http.c,v 1.80 2021/01/09 08:53:58 denis Exp $   */
+/*     $OpenBSD: relay_http.c,v 1.81 2021/03/24 20:59:54 benno Exp $   */
 
 /*
  * Copyright (c) 2006 - 2016 Reyk Floeter <reyk@openbsd.org>
@@ -112,6 +112,21 @@ relay_http_init(struct relay *rlay)
        relay_calc_skip_steps(&rlay->rl_proto->rules);
 }
 
+int
+relay_http_priv_init(struct rsession *con)
+{
+
+       struct http_session     *hs;
+
+       if ((hs = calloc(1, sizeof(*hs))) == NULL)
+               return (-1);
+       SIMPLEQ_INIT(&hs->hs_methods);
+       DPRINTF("%s: session %d http_session %p", __func__,
+               con->se_id, hs);
+       con->se_priv = hs;
+       return (relay_httpdesc_init(&con->se_in));
+}
+
 int
 relay_httpdesc_init(struct ctl_relay_event *cre)
 {
@@ -162,6 +177,9 @@ relay_read_http(struct bufferevent *bev, void *arg)
        size_t                   size, linelen;
        struct kv               *hdr = NULL;
        struct kv               *upgrade = NULL, *upgrade_ws = NULL;
+       struct http_method_node *hmn;
+       struct http_session     *hs;
+       enum httpmethod          request_method;
 
        getmonotime(&con->se_tv_last);
        cre->timedout = 0;
@@ -239,11 +257,34 @@ relay_read_http(struct bufferevent *bev, void *arg)
                DPRINTF("%s: session %d: header '%s: %s'", __func__,
                    con->se_id, key, value);
 
+               hs = con->se_priv;
+               DPRINTF("%s: session %d http_session %p", __func__,
+                       con->se_id, hs);
+
                /*
                 * Identify and handle specific HTTP request methods
                 */
                if (cre->line == 1 && cre->dir == RELAY_DIR_RESPONSE) {
                        desc->http_method = HTTP_METHOD_RESPONSE;
+                       hmn = SIMPLEQ_FIRST(&hs->hs_methods);
+
+                       /*
+                        * There is nothing preventing the relay to send
+                        * an unbalanced response.  Be prepared.
+                        */
+                       if (hmn == NULL) {
+                               request_method = HTTP_METHOD_NONE;
+                               DPRINTF("%s: session %d unbalanced response",
+                                   __func__, con->se_id);
+                       } else {
+                               SIMPLEQ_REMOVE_HEAD(&hs->hs_methods, hmn_entry);
+                               request_method = hmn->hmn_method;
+                               DPRINTF("%s: session %d dequeing %s", __func__,
+                                   con->se_id,
+                                   relay_httpmethod_byid(request_method));
+                               free(hmn);
+                       }
+
                        /*
                         * Decode response path and query
                         */
@@ -287,6 +328,15 @@ relay_read_http(struct bufferevent *bev, void *arg)
                                free(line);
                                goto fail;
                        }
+                       if ((hmn = calloc(1, sizeof *hmn)) == NULL) {
+                               free(line);
+                               goto fail;
+                       }
+                       hmn->hmn_method = desc->http_method;
+                       DPRINTF("%s: session %d enqueing %s", __func__,
+                           con->se_id,
+                           relay_httpmethod_byid(hmn->hmn_method));
+                       SIMPLEQ_INSERT_TAIL(&hs->hs_methods, hmn, hmn_entry);
                        /*
                         * Decode request path and query
                         */
@@ -332,16 +382,26 @@ relay_read_http(struct bufferevent *bev, void *arg)
                                goto abort;
                        }
                        /*
-                        * Need to read data from the client after the
-                        * HTTP header.
-                        * XXX What about non-standard clients not using
-                        * the carriage return? And some browsers seem to
-                        * include the line length in the content-length.
+                        * HEAD responses may provide a Content-Length header,
+                        * but if so it should just be ignored, since there is
+                        * no actual payload in the response.
                         */
-                       cre->toread = strtonum(value, 0, LLONG_MAX, &errstr);
-                       if (errstr) {
-                               relay_abort_http(con, 500, errstr, 0);
-                               goto abort;
+                       if (desc->http_method != HTTP_METHOD_RESPONSE
+                           || request_method != HTTP_METHOD_HEAD) {
+                               /*
+                                * Need to read data from the client after the
+                                * HTTP header.
+                                * XXX What about non-standard clients not
+                                * using the carriage return? And some browsers
+                                * seem to include the line length in the
+                                * content-length.
+                                */
+                               cre->toread = strtonum(value, 0, LLONG_MAX,
+                                   &errstr);
+                               if (errstr) {
+                                       relay_abort_http(con, 500, errstr, 0);
+                                       goto abort;
+                               }
                        }
                        /*
                         * response with a status code of 1xx
@@ -502,8 +562,9 @@ relay_read_http(struct bufferevent *bev, void *arg)
                case HTTP_METHOD_SEARCH:
                case HTTP_METHOD_PATCH:
                        /* HTTP request payload */
-                       if (cre->toread > 0)
+                       if (cre->toread > 0) {
                                bev->readcb = relay_read_httpcontent;
+                       }
 
                        /* Single-pass HTTP body */
                        if (cre->toread < 0) {
@@ -1117,6 +1178,19 @@ relay_abort_http(struct rsession *con, u_int code, const char *msg,
 void
 relay_close_http(struct rsession *con)
 {
+       struct http_session     *hs = con->se_priv;
+       struct http_method_node *hmn;
+
+       DPRINTF("%s: session %d http_session %p", __func__,
+               con->se_id, hs);
+       if (hs != NULL)
+               while (!SIMPLEQ_EMPTY(&hs->hs_methods)) {
+                       hmn = SIMPLEQ_FIRST(&hs->hs_methods);
+                       SIMPLEQ_REMOVE_HEAD(&hs->hs_methods, hmn_entry);
+                       DPRINTF("%s: session %d freeing %s", __func__,
+                           con->se_id, relay_httpmethod_byid(hmn->hmn_method));
+                       free(hmn);
+               }
        relay_httpdesc_free(con->se_in.desc);
        free(con->se_in.desc);
        relay_httpdesc_free(con->se_out.desc);