diff --git a/include/common/h2.h b/include/common/h2.h index e916732e9..6c1475bd9 100644 --- a/include/common/h2.h +++ b/include/common/h2.h @@ -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. diff --git a/src/h2.c b/src/h2.c index 1c8d30e17..1b784fd4a 100644 --- 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 from pseudo headers stored in . + * 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, 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 terminated by a name + * being and emits the equivalent HTX response according to the rules + * documented in RFC7540 #8.1.2. The output contents are emitted in , and + * a positive value is returned if some bytes were emitted. In case of error, a + * negative error code is returned. + * + * Upon success, 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 must be composed of : + * - n.name != NULL, n.len > 0 : literal header name + * - n.name == NULL, n.len > 0 : indexed pseudo header name number + * 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; +}