]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: h2: add new functions to produce an HTX message from an H2 response
authorWilly Tarreau <w@1wt.eu>
Mon, 8 Oct 2018 12:49:20 +0000 (14:49 +0200)
committerWilly Tarreau <w@1wt.eu>
Sun, 2 Dec 2018 12:30:17 +0000 (13:30 +0100)
The new function h2_prepare_htx_stsline() produces an HTX response message
from an H2 response presented as a list of header fields.

include/common/h2.h
src/h2.c

index e916732e9310f17fd682aac4f64621e515b92654..6c1475bd94efbd5259cc4d6583da7c053ccc64f2 100644 (file)
@@ -156,6 +156,7 @@ enum h2_err {
 
 int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf);
 int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf);
+int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf);
 
 /*
  * Some helpful debugging functions.
index 1c8d30e177b616785331442e5e203e4366262e37..1b784fd4aba2dbad91db156612243e93aa34e5f3 100644 (file)
--- a/src/h2.c
+++ b/src/h2.c
@@ -573,3 +573,168 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
  fail:
        return -1;
 }
+
+/* Prepare the status line into <htx> from pseudo headers stored in <phdr[]>.
+ * <fields> indicates what was found so far. This should be called once at the
+ * detection of the first general header field or at the end of the message if
+ * no general header field was found yet. Returns the created start line on
+ * success, or NULL on failure. Upon success, <msgf> is updated with a few
+ * H2_MSGF_* flags indicating what was found while parsing.
+ */
+static struct htx_sl *h2_prepare_htx_stsline(uint32_t fields, struct ist *phdr, struct htx *htx, unsigned int *msgf)
+{
+       unsigned int flags = HTX_SL_F_NONE;
+       struct htx_sl *sl;
+       unsigned char h, t, u;
+
+       /* only :status is allowed as a pseudo header */
+       if (!(fields & H2_PHDR_FND_STAT))
+               goto fail;
+
+       if (phdr[H2_PHDR_IDX_STAT].len != 3)
+               goto fail;
+
+       /* Set HTX start-line flags */
+       flags |= HTX_SL_F_VER_11;    // V2 in fact
+       flags |= HTX_SL_F_XFER_LEN;  // xfer len always known with H2
+
+       sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/2.0"), phdr[H2_PHDR_IDX_STAT], ist(""));
+       if (!sl)
+               goto fail;
+
+       h = phdr[H2_PHDR_IDX_STAT].ptr[0] - '0';
+       t = phdr[H2_PHDR_IDX_STAT].ptr[1] - '0';
+       u = phdr[H2_PHDR_IDX_STAT].ptr[2] - '0';
+       if (h > 9 || t > 9 || u > 9)
+               goto fail;
+
+       sl->info.res.status = h * 100 + t * 10 + u;
+
+       return sl;
+ fail:
+       return NULL;
+}
+
+/* Takes an H2 response present in the headers list <list> terminated by a name
+ * being <NULL,0> and emits the equivalent HTX response according to the rules
+ * documented in RFC7540 #8.1.2. The output contents are emitted in <htx>, and
+ * a positive value is returned if some bytes were emitted. In case of error, a
+ * negative error code is returned.
+ *
+ * Upon success, <msgf> is filled with a few H2_MSGF_* flags indicating what
+ * was found while parsing. The caller must set it to zero in or H2_MSGF_BODY
+ * if a body is detected (!ES).
+ *
+ * The headers list <list> must be composed of :
+ *   - n.name != NULL, n.len  > 0 : literal header name
+ *   - n.name == NULL, n.len  > 0 : indexed pseudo header name number <n.len>
+ *                                  among H2_PHDR_IDX_*
+ *   - n.name ignored, n.len == 0 : end of list
+ *   - in all cases except the end of list, v.name and v.len must designate a
+ *     valid value.
+ */
+int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf)
+{
+       struct ist phdr_val[H2_PHDR_NUM_ENTRIES];
+       uint32_t fields; /* bit mask of H2_PHDR_FND_* */
+       uint32_t idx;
+       int phdr;
+       int ret;
+       int i;
+       struct htx_sl *sl = NULL;
+       unsigned int sl_flags = 0;
+
+       fields = 0;
+       for (idx = 0; list[idx].n.len != 0; idx++) {
+               if (!list[idx].n.ptr) {
+                       /* this is an indexed pseudo-header */
+                       phdr = list[idx].n.len;
+               }
+               else {
+                       /* this can be any type of header */
+                       /* RFC7540#8.1.2: upper case not allowed in header field names */
+                       for (i = 0; i < list[idx].n.len; i++)
+                               if ((uint8_t)(list[idx].n.ptr[i] - 'A') < 'Z' - 'A')
+                                       goto fail;
+
+                       phdr = h2_str_to_phdr(list[idx].n);
+               }
+
+               if (phdr > 0 && phdr < H2_PHDR_NUM_ENTRIES) {
+                       /* insert a pseudo header by its index (in phdr) and value (in value) */
+                       if (fields & ((1 << phdr) | H2_PHDR_FND_NONE)) {
+                               if (fields & H2_PHDR_FND_NONE) {
+                                       /* pseudo header field after regular headers */
+                                       goto fail;
+                               }
+                               else {
+                                       /* repeated pseudo header field */
+                                       goto fail;
+                               }
+                       }
+                       fields |= 1 << phdr;
+                       phdr_val[phdr] = list[idx].v;
+                       continue;
+               }
+               else if (phdr != 0) {
+                       /* invalid pseudo header -- should never happen here */
+                       goto fail;
+               }
+
+               /* regular header field in (name,value) */
+               if (!(fields & H2_PHDR_FND_NONE)) {
+                       /* no more pseudo-headers, time to build the status line */
+                       sl = h2_prepare_htx_stsline(fields, phdr_val, htx, msgf);
+                       if (!sl)
+                               goto fail;
+                       fields |= H2_PHDR_FND_NONE;
+               }
+
+               if ((*msgf & (H2_MSGF_BODY|H2_MSGF_BODY_TUNNEL|H2_MSGF_BODY_CL)) == H2_MSGF_BODY &&
+                   isteq(list[idx].n, ist("content-length"))) {
+                       *msgf |= H2_MSGF_BODY_CL;
+                       sl_flags |= HTX_SL_F_CLEN;
+               }
+
+               /* these ones are forbidden in responses (RFC7540#8.1.2.2) */
+               if (isteq(list[idx].n, ist("connection")) ||
+                   isteq(list[idx].n, ist("proxy-connection")) ||
+                   isteq(list[idx].n, ist("keep-alive")) ||
+                   isteq(list[idx].n, ist("upgrade")) ||
+                   isteq(list[idx].n, ist("transfer-encoding")))
+                       goto fail;
+
+               if (!htx_add_header(htx, list[idx].n, list[idx].v))
+                       goto fail;
+       }
+
+       /* RFC7540#8.1.2.1 mandates to reject request pseudo-headers */
+       if (fields & (H2_PHDR_FND_AUTH|H2_PHDR_FND_METH|H2_PHDR_FND_PATH|H2_PHDR_FND_SCHM))
+               goto fail;
+
+       /* Let's dump the request now if not yet emitted. */
+       if (!(fields & H2_PHDR_FND_NONE)) {
+               sl = h2_prepare_htx_stsline(fields, phdr_val, htx, msgf);
+               if (!sl)
+                       goto fail;
+       }
+
+       /* update the start line with last detected header info */
+       sl->flags |= sl_flags;
+
+       if ((*msgf & (H2_MSGF_BODY|H2_MSGF_BODY_TUNNEL|H2_MSGF_BODY_CL)) == H2_MSGF_BODY) {
+               /* FIXME: Do we need to signal anything when we have a body and
+                * no content-length, to have the equivalent of H1's chunked
+                * encoding?
+                */
+       }
+
+       /* now send the end of headers marker */
+       htx_add_endof(htx, HTX_BLK_EOH);
+
+       ret = 1;
+       return ret;
+
+ fail:
+       return -1;
+}