|
|
|
/*
|
|
|
|
* h2 - HTTP/2 protocol layer
|
|
|
|
*
|
|
|
|
* Copyright(c) 2020 Glenn Strauss gstrauss()gluelogic.com All rights reserved
|
|
|
|
* License: BSD 3-clause (same as lighttpd)
|
|
|
|
*/
|
|
|
|
#include "first.h"
|
|
|
|
#include "h2.h"
|
|
|
|
|
|
|
|
#include <arpa/inet.h> /* htonl() */
|
|
|
|
#include <stdint.h> /* INT32_MAX INT32_MIN */
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "base.h"
|
|
|
|
#include "buffer.h"
|
|
|
|
#include "chunk.h"
|
|
|
|
#include "fdevent.h" /* FDEVENT_STREAM_REQUEST_BUFMIN */
|
|
|
|
#include "http_header.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include "request.h"
|
|
|
|
#include "response.h" /* http_response_omit_header() */
|
|
|
|
|
|
|
|
|
|
|
|
static request_st * h2_init_stream (request_st * const h2r, connection * const con);
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_send_settings_ack (connection * const con)
|
|
|
|
{
|
|
|
|
static const uint8_t settings_ack[] = {
|
|
|
|
/* SETTINGS w/ ACK */
|
|
|
|
0x00, 0x00, 0x00 /* frame length */
|
|
|
|
,H2_FTYPE_SETTINGS /* frame type */
|
|
|
|
,H2_FLAG_ACK /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier */
|
|
|
|
};
|
|
|
|
|
|
|
|
chunkqueue_append_mem(con->write_queue,
|
|
|
|
(const char *)settings_ack, sizeof(settings_ack));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_rst_stream_id (uint32_t h2id, connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
union {
|
|
|
|
uint8_t c[16];
|
|
|
|
uint32_t u[4]; /*(alignment)*/
|
|
|
|
} rst_stream = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* RST_STREAM */
|
|
|
|
,0x00, 0x00, 0x04 /* frame length */
|
|
|
|
,H2_FTYPE_RST_STREAM /* frame type */
|
|
|
|
,0x00 /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* error code; (fill in below) */
|
|
|
|
} };
|
|
|
|
|
|
|
|
rst_stream.u[2] = htonl(h2id);
|
|
|
|
rst_stream.u[3] = htonl(e);
|
|
|
|
chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align padding)*/
|
|
|
|
(const char *)rst_stream.c+3, sizeof(rst_stream)-3);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_rst_stream (request_st * const r, connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
r->state = CON_STATE_ERROR;
|
|
|
|
r->h2state = H2_STATE_CLOSED;
|
|
|
|
h2_send_rst_stream_id(r->h2id, con, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_goaway_rst_stream (connection * const con)
|
|
|
|
{
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
const int sent_goaway = h2c->sent_goaway;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
if (r->h2state == H2_STATE_CLOSED) continue;
|
|
|
|
/*(XXX: might consider always sending RST_STREAM)*/
|
|
|
|
if (!sent_goaway) {
|
|
|
|
r->state = CON_STATE_ERROR;
|
|
|
|
r->h2state = H2_STATE_CLOSED;
|
|
|
|
}
|
|
|
|
else /*(also sets r->h2state = H2_STATE_CLOSED)*/
|
|
|
|
h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
h2_send_goaway (connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
/* future: RFC 7540 Section 6.8 notes that server initiating graceful
|
|
|
|
* connection shutdown SHOULD send GOAWAY with stream id 2^31-1 and a
|
|
|
|
* NO_ERROR code, and later send another GOAWAY with an updated last
|
|
|
|
* stream identifier. (This is not done here, but doing so would be
|
|
|
|
* friendlier to clients that send streaming requests which the client
|
|
|
|
* is unable to retry.) */
|
|
|
|
|
|
|
|
if (e != H2_E_NO_ERROR)
|
|
|
|
h2_send_goaway_rst_stream(con);
|
|
|
|
/*XXX: else should send RST_STREAM w/ CANCEL for any active PUSH_PROMISE */
|
|
|
|
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
if (h2c->sent_goaway && (h2c->sent_goaway > 0 || e == H2_E_NO_ERROR))
|
|
|
|
return;
|
|
|
|
h2c->sent_goaway = (e == H2_E_NO_ERROR) ? -1 : (int32_t)e;
|
|
|
|
|
|
|
|
union {
|
|
|
|
uint8_t c[20];
|
|
|
|
uint32_t u[5]; /*(alignment)*/
|
|
|
|
} goaway = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* GOAWAY */
|
|
|
|
,0x00, 0x00, 0x08 /* frame length */
|
|
|
|
,H2_FTYPE_GOAWAY /* frame type */
|
|
|
|
,0x00 /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* last-stream-id (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* error code (fill in below) */
|
|
|
|
/* additional debug data (*); (optional)
|
|
|
|
* adjust frame length if any additional
|
|
|
|
* debug data is sent */
|
|
|
|
} };
|
|
|
|
|
|
|
|
goaway.u[3] = htonl(h2c->h2_cid); /* last-stream-id */
|
|
|
|
goaway.u[4] = htonl(e);
|
|
|
|
chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align padding)*/
|
|
|
|
(const char *)goaway.c+3, sizeof(goaway)-3);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static void
|
|
|
|
h2_send_goaway_e (connection * const con, const request_h2error_t e)
|
|
|
|
{
|
|
|
|
h2_send_goaway(con, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
h2_recv_goaway (connection * const con, const uint8_t * const s, uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire GOAWAY frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_GOAWAY);*/
|
|
|
|
UNUSED(len);
|
|
|
|
if ((s[5] & ~0x80) | s[6] | s[7] | s[8]) { /*(GOAWAY stream id must be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const uint32_t e = (s[13]<< 24) | (s[14]<< 16) | (s[15]<< 8) | s[16];
|
|
|
|
#if 0
|
|
|
|
/* XXX: debug: could log error code sent by peer */
|
|
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
/* XXX: debug: could log additional debug info (if any) sent by peer */
|
|
|
|
if (len > 8) {
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#if 0
|
|
|
|
/* XXX: could validate/use Last-Stream-ID sent by peer */
|
|
|
|
const uint32_t last_id = (s[9] << 24) | (s[10]<< 16) | (s[11]<< 8) | s[12];
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* send PROTOCOL_ERROR back to peer if peer sent an error code
|
|
|
|
* (i.e. not NO_ERROR) in order to terminate connection more quickly */
|
|
|
|
h2_send_goaway(con, e==H2_E_NO_ERROR ? H2_E_NO_ERROR : H2_E_PROTOCOL_ERROR);
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
if (0 == h2c->rused) return 0;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_rst_stream (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire RST_STREAM frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_RST_STREAM);*/
|
|
|
|
if (4 != len) { /*(RST_STREAM frame length must be 4)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t id =
|
|
|
|
((s[5] << 24) | (s[6] << 16) | (s[7] << 8) | s[8]) & ~0x80000000u;
|
|
|
|
if (0 == id) { /*(RST_STREAM id must not be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
if (r->h2id != id) continue;
|
|
|
|
if (r->h2state == H2_STATE_IDLE) {
|
|
|
|
/*(RST_STREAM must not be for stream in "idle" state)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* XXX: ? add debug trace including error code from RST_STREAM ? */
|
|
|
|
r->state = CON_STATE_ERROR;
|
|
|
|
r->h2state = H2_STATE_CLOSED;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* unknown/inactive stream id
|
|
|
|
* XXX: how should we handle RST_STREAM for unknown/inactive stream id?
|
|
|
|
* (stream id may have been closed recently and server forgot about it,
|
|
|
|
* but client (peer) sent RST_STREAM prior to receiving stream end from
|
|
|
|
* server)*/
|
|
|
|
#if 0
|
|
|
|
if (h2c->sent_goaway && h2c->h2_cid < id) return;
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
#else
|
|
|
|
if (h2c->h2_cid < id) {
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_ping (connection * const con, uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
union {
|
|
|
|
uint8_t c[20];
|
|
|
|
uint32_t u[5]; /*(alignment)*/
|
|
|
|
} ping = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* PING */
|
|
|
|
,0x00, 0x00, 0x08 /* frame length */
|
|
|
|
,H2_FTYPE_PING /* frame type */
|
|
|
|
,H2_FLAG_ACK /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* opaque (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00
|
|
|
|
} };
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*(s must be entire PING frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_PING);*/
|
|
|
|
if (8 != len) { /*(PING frame length must be 8)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
s[5] &= ~0x80; /* reserved bit must be ignored */
|
|
|
|
if (s[5] | s[6] | s[7] | s[8]) { /*(PING stream id must be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (s[4] & H2_FLAG_ACK) /*(ignore; unexpected if we did not send PING)*/
|
|
|
|
return;
|
|
|
|
/* reflect PING back to peer with frame flag ACK */
|
|
|
|
/* (9 byte frame header plus 8 byte PING payload = 17 bytes)*/
|
|
|
|
s[4] = H2_FLAG_ACK;
|
|
|
|
chunkqueue_append_mem(con->write_queue, (const char *)s, 17);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_priority (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire PRIORITY frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_PRIORITY);*/
|
|
|
|
if (5 != len) { /*(PRIORITY frame length must be 5)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t id =
|
|
|
|
((s[5] << 24) | (s[6] << 16) | (s[7] << 8) | s[8]) & ~0x80000000u;
|
|
|
|
if (0 == id) { /*(PRIORITY id must not be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t prio =
|
|
|
|
((s[9] << 24) | (s[10] << 16) | (s[11] << 8) | s[12]) & ~0x80000000u;
|
|
|
|
#if 0
|
|
|
|
uint32_t exclusive_dependency = (s[9] & 0x80) ? 1 : 0;
|
|
|
|
uint32_t weight = s[13];
|
|
|
|
#endif
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
if (r->h2id != id) continue;
|
|
|
|
/* XXX: TODO: update priority info */
|
|
|
|
if (prio == id) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* XXX: TODO: update priority info for unknown/inactive stream */
|
|
|
|
/*if (h2c->sent_goaway && h2c->h2_cid < id) return;*/
|
|
|
|
if (prio == id) {
|
|
|
|
h2_send_rst_stream_id(id, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_window_update (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire WINDOW_UPDATE frame and len the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_WINDOW_UPDATE);*/
|
|
|
|
if (4 != len) { /*(WINDOW_UPDATE frame length must be 4)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const uint32_t id =
|
|
|
|
((s[5] << 24) | (s[6] << 16) | (s[7] << 8) | s[8]) & ~0x80000000u;
|
|
|
|
const int32_t v =
|
|
|
|
(int32_t)(((s[9] << 24)|(s[10] << 16)|(s[11] << 8)|s[12]) & ~0x80000000);
|
|
|
|
request_st *r = NULL;
|
|
|
|
if (0 == id)
|
|
|
|
r = &con->request;
|
|
|
|
else {
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const rr = h2c->r[i];
|
|
|
|
if (rr->h2id != id) continue;
|
|
|
|
r = rr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* peer should not send WINDOW_UPDATE for an inactive stream,
|
|
|
|
* but RFC 7540 does not explicitly call this out. On the other hand,
|
|
|
|
* since there may be a temporary mismatch in stream state between
|
|
|
|
* peers, ignore window update if stream id is unknown/inactive.
|
|
|
|
* Also, it is not an error if GOAWAY sent and h2c->h2_cid < id */
|
|
|
|
if (NULL == r) {
|
|
|
|
if (h2c->h2_cid < id && 0 == h2c->sent_goaway)
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
#if 0
|
|
|
|
/*(needed for h2spec if testing with response < 16k+1 over TLS
|
|
|
|
* or response <= socket send buffer size over cleartext, due to
|
|
|
|
* completing response too quickly for the test frame sequence) */
|
|
|
|
if (v == 0) /* h2spec: 6.9-2 (after we retired id 1) */
|
|
|
|
h2_send_rst_stream_id(id, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
if (v == INT32_MAX)/* h2spec: 6.9.1-3 (after we retired id 1) */
|
|
|
|
h2_send_rst_stream_id(id, con, H2_E_FLOW_CONTROL_ERROR);
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* MUST NOT be treated as error if stream is in closed state; ignore */
|
|
|
|
if (r->h2state == H2_STATE_CLOSED
|
|
|
|
|| r->h2state == H2_STATE_HALF_CLOSED_LOCAL) return;
|
|
|
|
}
|
|
|
|
if (0 == v || r->h2_swin > INT32_MAX - v) {
|
|
|
|
request_h2error_t e = (0 == v)
|
|
|
|
? H2_E_PROTOCOL_ERROR
|
|
|
|
: H2_E_FLOW_CONTROL_ERROR;
|
|
|
|
if (0 == id)
|
|
|
|
h2_send_goaway_e(con, e);
|
|
|
|
else
|
|
|
|
h2_send_rst_stream(r, con, e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
r->h2_swin += v;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_send_window_update (connection * const con, uint32_t h2id, const uint32_t len)
|
|
|
|
{
|
|
|
|
if (0 == len) return;
|
|
|
|
union {
|
|
|
|
uint8_t c[16];
|
|
|
|
uint32_t u[4]; /*(alignment)*/
|
|
|
|
} window_upd = { { /*(big-endian numbers)*/
|
|
|
|
0x00, 0x00, 0x00 /* padding for alignment; do not send */
|
|
|
|
/* WINDOW_UPDATE */
|
|
|
|
,0x00, 0x00, 0x04 /* frame length */
|
|
|
|
,H2_FTYPE_WINDOW_UPDATE /* frame type */
|
|
|
|
,0x00 /* frame flags */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* stream identifier (fill in below) */
|
|
|
|
,0x00, 0x00, 0x00, 0x00 /* window update increase (fill in below) */
|
|
|
|
} };
|
|
|
|
|
|
|
|
window_upd.u[2] = htonl(h2id);
|
|
|
|
window_upd.u[3] = htonl(len);
|
|
|
|
chunkqueue_append_mem(con->write_queue, /*(+3 to skip over align padding)*/
|
|
|
|
(const char *)window_upd.c+3, sizeof(window_upd)-3);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_parse_frame_settings (connection * const con, const uint8_t *s, uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s and len must be SETTINGS frame payload)*/
|
|
|
|
/*(caller must validate frame len, frame type == 0x04, frame id == 0)*/
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
for (; len >= 6; len -= 6, s += 6) {
|
|
|
|
uint32_t v = (s[2] << 24) | (s[3] << 16) | (s[4] << 8) | s[5];
|
|
|
|
switch (((s[0] << 8) | s[1])) {
|
|
|
|
case H2_SETTINGS_HEADER_TABLE_SIZE:
|
|
|
|
/* encoder may use any table size <= value sent by peer */
|
|
|
|
/* For simple compliance with RFC and constrained memory use,
|
|
|
|
* choose to not increase table size beyond the default 4096,
|
|
|
|
* but allow smaller sizes to be set and then reset up to 4096,
|
|
|
|
* e.g. set to 0 to evict all dynamic table entries,
|
|
|
|
* and then set to 4096 to restore dynamic table use */
|
|
|
|
if (v > 4096) v = 4096;
|
|
|
|
if (v == h2c->s_header_table_size) break;
|
|
|
|
h2c->s_header_table_size = v;
|
|
|
|
lshpack_enc_set_max_capacity(&h2c->encoder, v);
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_ENABLE_PUSH:
|
|
|
|
if ((v|1) != 1) { /*(v == 0 || v == 1)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h2c->s_enable_push = v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
|
|
|
h2c->s_max_concurrent_streams = v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_INITIAL_WINDOW_SIZE:
|
|
|
|
if (v > INT32_MAX) { /*(2^31 - 1)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FLOW_CONTROL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (h2c->rused) { /*(update existing streams)*/
|
|
|
|
/*(underflow is ok; unsigned integer math)*/
|
|
|
|
/*(h2c->s_initial_window_size is >= 0)*/
|
|
|
|
int32_t diff =
|
|
|
|
(int32_t)((uint32_t)v - (uint32_t)h2c->s_initial_window_size);
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const r = h2c->r[i];
|
|
|
|
const int32_t swin = r->h2_swin;
|
|
|
|
if (r->h2state == H2_STATE_HALF_CLOSED_LOCAL
|
|
|
|
|| r->h2state == H2_STATE_CLOSED) continue;
|
|
|
|
if (diff >= 0
|
|
|
|
? swin > INT32_MAX - diff
|
|
|
|
: swin < INT32_MIN - diff) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_FLOW_CONTROL_ERROR);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
r->h2_swin += diff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
h2c->s_initial_window_size = (int32_t)v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_MAX_FRAME_SIZE:
|
|
|
|
if (v < 16384 || v > 16777215) { /*[(2^14),(2^24-1)]*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
h2c->s_max_frame_size = v;
|
|
|
|
break;
|
|
|
|
case H2_SETTINGS_MAX_HEADER_LIST_SIZE:
|
|
|
|
h2c->s_max_header_list_size = v;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len) {
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* caller must send SETTINGS frame with ACK flag,
|
|
|
|
* if appropriate, and if h2c->sent_goaway is not set
|
|
|
|
* (Do not send ACK for Upgrade: h2c and HTTP2-Settings header) */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_recv_settings (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire SETTINGS frame, len must be the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_SETTINGS);*/
|
|
|
|
if ((s[5] & ~0x80) | s[6] | s[7] | s[8]) {/*(SETTINGS stream id must be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
if (!(s[4] & H2_FLAG_ACK)) {
|
|
|
|
h2_parse_frame_settings(con, s+9, len);
|
|
|
|
if (h2c->sent_goaway <= 0)
|
|
|
|
h2_send_settings_ack(con);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* lighttpd currently sends SETTINGS in server preface, and not again,
|
|
|
|
* so this does not have to handle another SETTINGS frame being sent
|
|
|
|
* before receiving an ACK from prior SETTINGS frame. (If it does,
|
|
|
|
* then we will need some sort of counter.) */
|
|
|
|
if (0 != len)
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
else if (h2c->sent_settings)
|
|
|
|
h2c->sent_settings = 0;
|
|
|
|
else /* SETTINGS with ACK for SETTINGS frame we did not send */
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
h2_recv_end_data (request_st * const r, connection * const con, const uint32_t alen)
|
|
|
|
{
|
|
|
|
chunkqueue * const reqbody_queue = r->reqbody_queue;
|
|
|
|
r->h2state = (r->h2state == H2_STATE_OPEN)
|
|
|
|
? H2_STATE_HALF_CLOSED_REMOTE
|
|
|
|
: H2_STATE_CLOSED;
|
|
|
|
if (r->reqbody_length == -1)
|
|
|
|
r->reqbody_length = reqbody_queue->bytes_in + (off_t)alen;
|
|
|
|
else if (r->reqbody_length != reqbody_queue->bytes_in + (off_t)alen) {
|
|
|
|
if (0 == reqbody_queue->bytes_out) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
} /* else let reqbody streaming consumer handle truncated reqbody */
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
h2_recv_data (connection * const con, const uint8_t * const s, const uint32_t len)
|
|
|
|
{
|
|
|
|
/*(s must be entire DATA frame, len must be the frame length field)*/
|
|
|
|
/*assert(s[3] == H2_FTYPE_DATA);*/
|
|
|
|
|
|
|
|
/* future: consider string refs rather than copying DATA from chunkqueue
|
|
|
|
* or try to consume entire chunk, or to split chunks with less copying */
|
|
|
|
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
const uint32_t id =
|
|
|
|
((s[5] << 24) | (s[6] << 16) | (s[7] << 8) | s[8]) & ~0x80000000u;
|
|
|
|
if (0 == id || h2c->h2_cid < id) { /*(RST_STREAM id must not be 0)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t alen = len; /* actual data len, minus padding */
|
|
|
|
uint32_t pad = 0;
|
|
|
|
if (s[4] & H2_FLAG_PADDED) {
|
|
|
|
pad = s[9];
|
|
|
|
if (pad >= len) {
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
alen -= (1 + pad);
|
|
|
|
}
|
|
|
|
|
|
|
|
request_st * const h2r = &con->request;
|
|
|
|
if (h2r->h2_rwin <= 0 && 0 != alen) { /*(always proceed if 0 == alen)*/
|
|
|
|
/*(connection_state_machine_h2() must ensure con is rescheduled,
|
|
|
|
* when backends consume data if con->read_queue is not empty,
|
|
|
|
* whether or not con->fd has data to read from the network)*/
|
|
|
|
/*(leave frame in cq to be re-read later)*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*(allow h2r->h2_rwin to dip below 0 so that entire frame is processed)*/
|
|
|
|
/*(not worried about underflow while
|
|
|
|
* SETTINGS_MAX_FRAME_SIZE is small (e.g. 16k or 32k) and
|
|
|
|
* SETTINGS_MAX_CONCURRENT_STREAMS is small (h2c->r[8]))*/
|
|
|
|
/*h2r->h2_rwin -= (int32_t)len;*//* update connection recv window (below) */
|
|
|
|
|
|
|
|
request_st *r = NULL;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const rr = h2c->r[i];
|
|
|
|
if (rr->h2id != id) continue;
|
|
|
|
r = rr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
chunkqueue * const cq = con->read_queue;
|
|
|
|
if (NULL == r) {
|
|
|
|
/* XXX: TODO: might need to keep a list of recently retired streams
|
|
|
|
* for a few seconds so that if we send RST_STREAM, then we ignore
|
|
|
|
* further DATA and do not send connection error, though recv windows
|
|
|
|
* still must be updated. */
|
|
|
|
if (h2c->h2_cid < id || (!h2c->sent_goaway && 0 != alen))
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
chunkqueue_mark_written(cq, 9+len);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r->h2state == H2_STATE_CLOSED
|
|
|
|
|| r->h2state == H2_STATE_HALF_CLOSED_REMOTE) {
|
|
|
|
h2_send_rst_stream_id(id, con, H2_E_STREAM_CLOSED);
|
|
|
|
chunkqueue_mark_written(cq, 9+len);
|
|
|
|
h2_send_window_update(con, 0, len); /*(h2r->h2_rwin)*/
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r->h2_rwin <= 0 && 0 != alen) {/*(always proceed if 0==alen)*/
|
|
|
|
if (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN) {
|
|
|
|
/*(connection_state_machine_h2() must ensure con is rescheduled,
|
|
|
|
* when backends consume data if con->read_queue is not empty,
|
|
|
|
* whether or not con->fd has data to read from the network)*/
|
|
|
|
/*(leave frame in cq to be re-read later)*/
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*(allow h2r->h2_rwin to dip below 0 so that entire frame is processed)*/
|
|
|
|
/*(undeflow will not occur (with reasonable SETTINGS_MAX_FRAME_SIZE used)
|
|
|
|
* since windows updated elsewhere and data is streamed to temp files if
|
|
|
|
* not FDEVENT_STREAM_REQUEST_BUFMIN)*/
|
|
|
|
/*r->h2_rwin -= (int32_t)len;*/
|
|
|
|
h2_send_window_update(con, r->h2id, len); /*(r->h2_rwin)*/
|
|
|
|
h2_send_window_update(con, 0, len); /*(h2r->h2_rwin)*/
|
|
|
|
|
|
|
|
chunkqueue * const dst = r->reqbody_queue;
|
|
|
|
|
|
|
|
if (r->reqbody_length >= 0 && r->reqbody_length < dst->bytes_in + alen) {
|
|
|
|
/* data exceeds Content-Length specified (client mistake) */
|
|
|
|
#if 0 /* truncate */
|
|
|
|
alen = r->reqbody_length - dst->bytes_in;
|
|
|
|
/*(END_STREAM may follow in 0-length DATA frame or HEADERS (trailers))*/
|
|
|
|
#else /* reject */
|
|
|
|
h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
chunkqueue_mark_written(cq, 9+len);
|
|
|
|
return 1;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*(accounting for mod_accesslog and mod_rrdtool)*/
|
|
|
|
chunkqueue * const rq = r->read_queue;
|
|
|
|
rq->bytes_in += (off_t)alen;
|
|
|
|
rq->bytes_out += (off_t)alen;
|
|
|
|
|
|
|
|
/* r->conf.max_request_size is in kBytes */
|
|
|
|
const off_t max_request_size = (off_t)r->conf.max_request_size << 10;
|
|
|
|
if (0 != max_request_size
|
|
|
|
&& dst->bytes_in + (off_t)alen > max_request_size) {
|
|
|
|
if (0 == r->http_status) {
|
|
|
|
r->http_status = 413; /* Payload Too Large */
|
|
|
|
log_error(r->conf.errh, __FILE__, __LINE__,
|
|
|
|
"request-size too long: %lld -> 413",
|
|
|
|
(long long) (dst->bytes_in + (off_t)alen));
|
|
|
|
}
|
|
|
|
chunkqueue_mark_written(cq, 9+len);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((s[4] & H2_FLAG_END_STREAM) && !h2_recv_end_data(r, con, alen)) {
|
|
|
|
chunkqueue_mark_written(cq, 9+len);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
chunkqueue_mark_written(cq, 9 + ((s[4] & H2_FLAG_PADDED) ? 1 : 0));
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
if (pad) {
|
|
|
|
/* XXX: future optimization: if data is at end of chunk, then adjust
|
|
|
|
* size of chunk by reducing c->mem->used to avoid copying chunk
|
|
|
|
* when it is split (below) since the split would be due to padding
|
|
|
|
* (also adjust cq->bytes_out)*/
|
|
|
|
/*(might quickly check 9+len == cqlen if cqlen passed in as param)*/
|
|
|
|
/*(then check if cq->last contains all of padding, or leave alone)*/
|
|
|
|
/*(if handled here, then set pad = 0 here)*/
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*(similar decision logic to that in http_chunk_uses_tempfile())*/
|
|
|
|
const chunk * const c = dst->last;
|
|
|
|
if ((c && c->type == FILE_CHUNK && c->file.is_temp)
|
|
|
|
|| dst->bytes_in - dst->bytes_out + alen > 65536) {
|
|
|
|
log_error_st * const errh = r->conf.errh;
|
|
|
|
if (0 != chunkqueue_steal_with_tempfiles(dst, cq, (off_t)alen, errh)) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_INTERNAL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
chunkqueue_steal(dst, cq, (off_t)alen);
|
|
|
|
|
|
|
|
if (pad)
|
|
|
|
chunkqueue_mark_written(cq, pad);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static uint32_t
|
|
|
|
h2_frame_cq_compact (chunkqueue * const cq, uint32_t len)
|
|
|
|
{
|
|
|
|
/*(marked cold since most frames not expect to cross chunk boundary)*/
|
|
|
|
|
|
|
|
/*(must be guaranteed by caller)*/
|
|
|
|
/*assert(chunkqueue_length(cq) >= len);*/
|
|
|
|
/*assert(cq->first != cq->last);*//*(multiple chunks)*/
|
|
|
|
/* caller must guarantee that chunks in chunkqueue are all MEM_CHUNK */
|
|
|
|
|
|
|
|
/* move data to beginning of buffer if offset is large or data is short */
|
|
|
|
chunk *c = cq->first;
|
|
|
|
uint32_t mlen = buffer_string_length(c->mem);
|
|
|
|
if (mlen < c->offset) {
|
|
|
|
memmove(c->mem->ptr, c->mem->ptr + c->offset, mlen);
|
|
|
|
buffer_string_set_length(c->mem, mlen);
|
|
|
|
c->offset = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* combine first mem chunk with next non-empty mem chunks up to len
|
|
|
|
* (loop if next chunk is empty) */
|
|
|
|
/* (modified from connection_handle_read_post_cq_compact()) */
|
|
|
|
uint32_t clen = mlen;
|
|
|
|
do {
|
|
|
|
buffer * const mem = c->next->mem;
|
|
|
|
const off_t offset = c->next->offset;
|
|
|
|
mlen = buffer_string_length(mem) - (uint32_t)offset;
|
|
|
|
force_assert(c->type == MEM_CHUNK);
|
|
|
|
force_assert(c->next->type == MEM_CHUNK);
|
|
|
|
if (mlen > clen - len) {
|
|
|
|
mlen = clen - len;
|
|
|
|
buffer_append_string_len(c->mem, mem->ptr+offset, mlen);
|
|
|
|
c->next->offset += mlen;
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer_append_string_len(c->mem, mem->ptr+offset, mlen);
|
|
|
|
clen += mlen;
|
|
|
|
/*(swap first and second chunk, then remove first chunk)*/
|
|
|
|
c->next->offset = c->offset;
|
|
|
|
c->next->mem = c->mem;
|
|
|
|
c->mem = mem;
|
|
|
|
c->offset = offset + (off_t)mlen;
|
|
|
|
chunkqueue_remove_finished_chunks(cq);
|
|
|
|
} while ((c = cq->first)); /*(need to re-read cq->first)*/
|
|
|
|
return clen;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static uint32_t
|
|
|
|
h2_recv_continuation (uint32_t n, uint32_t clen, const off_t cqlen, chunkqueue * const cq, connection * const con)
|
|
|
|
{
|
|
|
|
chunk *c = cq->first;
|
|
|
|
uint8_t *s = (uint8_t *)(c->mem->ptr + c->offset);
|
|
|
|
uint32_t m = n;
|
|
|
|
uint32_t flags;
|
|
|
|
h2con * const h2c = con->h2;
|
|
|
|
const uint32_t fsize = h2c->s_max_frame_size;
|
|
|
|
const uint32_t id =
|
|
|
|
((s[5] << 24) | (s[6] << 16) | (s[7] << 8) | s[8]) & ~0x80000000u;
|
|
|
|
do {
|
|
|
|
if (cqlen < n+9) return n+9; /* incomplete frame; go on */
|
|
|
|
if (clen < n+9) {
|
|
|
|
clen = h2_frame_cq_compact(cq, n+9);
|
|
|
|
c = cq->first; /*(reload after h2_frame_cq_compact())*/
|
|
|
|
s = (uint8_t *)(c->mem->ptr + c->offset);
|
|
|
|
}
|
|
|
|
if (s[n+3] != H2_FTYPE_CONTINUATION) {
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
flags = s[n+4];
|
|
|
|
const uint32_t flen = (s[n+0]<<16)|(s[n+1]<<8)|s[n+2];
|
|
|
|
if (id != (uint32_t)((s[n+5]<<24)|(s[n+6]<<16)|(s[n+7]<<8)|s[n+8])) {
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (flen > fsize) {
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
n += 9+flen;
|
|
|
|
if (n >= 65536) { /*(very oversized for hpack)*/
|
|
|
|
h2_send_goaway_e(con, H2_E_FRAME_SIZE_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (clen < n) {
|
|
|
|
clen = h2_frame_cq_compact(cq, n);
|
|
|
|
c = cq->first; /*(reload after h2_frame_cq_compact())*/
|
|
|
|
s = (uint8_t *)(c->mem->ptr + c->offset);
|
|
|
|
}
|
|
|
|
} while (!(flags & H2_FLAG_END_HEADERS));
|
|
|
|
|
|
|
|
/* If some CONTINUATION frames were concatenated to earlier frames while
|
|
|
|
* processing above, but END_HEADERS were not received, then the next time
|
|
|
|
* data was read, initial frame size might exceed SETTINGS_MAX_FRAME_SIZE.
|
|
|
|
* (This describes the current lighttpd implementation in h2_parse_frames())
|
|
|
|
* While a flag could be set and checked to avoid this, such situations of
|
|
|
|
* large HEADERS (and CONTINUATION) across multiple network reads is
|
|
|
|
* expected to be rare. Reparse and concatenate below.
|
|
|
|
*
|
|
|
|
* Aside: why would the authors of RFC 7540 go through the trouble of
|
|
|
|
* creating a CONTINUATION frame that must be special-cased when use of
|
|
|
|
* CONTINUATION is so restricted e.g. no other intervening frames and
|
|
|
|
* that HEADERS and PUSH_PROMISE HPACK must be parsed as a single block?
|
|
|
|
* IMHO, it would have been simpler to avoid CONTINUATION entirely, and have
|
|
|
|
* a special-case for HEADERS and PUSH_PROMISE to be allowed to exceed
|
|
|
|
* SETTINGS_MAX_FRAME_SIZE with implementations providing a different limit.
|
|
|
|
* While intermediates would not know such a limit of origin servers,
|
|
|
|
* there could have been a reasonable default set with a different SETTINGS
|
|
|
|
* parameter aimed just at HEADERS and PUSH_PROMISE. The parameter
|
|
|
|
* SETTINGS_MAX_HEADER_LIST_SIZE could even have been (re)used, had it been
|
|
|
|
* given a reasonable initial value instead of "unlimited", since HPACK
|
|
|
|
* encoded headers are smaller than the HPACK decoded headers to which the
|
|
|
|
* limit SETTINGS_MAX_HEADER_LIST_SIZE applies. */
|
|
|
|
|
|
|
|
n = m; /* reset n to beginning of first CONTINUATION frame */
|
|
|
|
|
|
|
|
/* Eliminate padding from first frame (HEADERS or PUSH_PROMISE) if PADDED */
|
|
|
|
if (s[4] & H2_FLAG_PADDED) {
|
|
|
|
const uint32_t plen = s[9];
|
|
|
|
/* validate padding */
|
|
|
|
const uint32_t flen = (s[0]<<16)|(s[1]<<8)|s[2];
|
|
|
|
if (flen < 1 + plen + ((s[n+4] & H2_FLAG_PRIORITY) ? 5 : 0)) {
|
|
|
|
/* Padding that exceeds the size remaining for the header block
|
|
|
|
* fragment MUST be treated as a PROTOCOL_ERROR. */
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* set padding to 0 since we will overwrite padding in merge below */
|
|
|
|
/* (alternatively, could memmove() 9 bytes of frame header over the
|
|
|
|
* pad length octet, remove PADDED flag, add 1 to c->offset,
|
|
|
|
* add 1 to s, subtract 1 from clen and substract 1 from cqlen,
|
|
|
|
* substract 1 from n) */
|
|
|
|
s[9] = 0;
|
|
|
|
/* set offset to beginning of padding at end of first frame */
|
|
|
|
m -= plen;
|
|
|
|
}
|
|
|
|
|
|
|
|
do {
|
|
|
|
const uint32_t flen = (s[n+0]<<16)|(s[n+1]<<8)|s[n+2];
|
|
|
|
flags = s[n+4];
|
|
|
|
memmove(s+m, s+n+9, flen);
|
|
|
|
m += flen;
|
|
|
|
n += 9+flen;
|
|
|
|
} while (!(flags & H2_FLAG_END_HEADERS));
|
|
|
|
/* overwrite frame size */
|
|
|
|
m -= 9; /*(temporarily remove frame header from len)*/
|
|
|
|
s[0] = (m >> 16) & 0xFF;
|
|
|
|
s[1] = (m >> 8) & 0xFF;
|
|
|
|
s[2] = (m ) & 0xFF;
|
|
|
|
m += 9;
|
|
|
|
/* adjust chunk c->mem */
|
|
|
|
if (n < clen) { /*(additional frames after CONTINUATION)*/
|
|
|
|
memmove(s+m, s+n, clen-n);
|
|
|
|
n = m + (clen-n);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
n = m;
|
|
|
|
buffer_string_set_length(c->mem, n + (uint32_t)c->offset);
|
|
|
|
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
__attribute_cold__
|
|
|
|
static request_st *
|
|
|
|
h2_recv_trailers_r (connection * const con, h2con * const h2c, const uint32_t id, const uint32_t flags)
|
|
|
|
{
|
|
|
|
/* rant: RFC 7230 HTTP/1.1 trailer-part would have been much simpler
|
|
|
|
* to support in RFC 7540 HTTP/2 as a distinct frame type rather than
|
|
|
|
* HEADERS. As trailers are not known at the time the request is made,
|
|
|
|
* reuse of such trailers is limited and so a theoretical TRAILERS frame
|
|
|
|
* could have been implemented without HPACK encoding, and would have
|
|
|
|
* been more straightforward to implement than overloading and having to
|
|
|
|
* handle multiple cases for HEADERS. TRAILERS support could then also
|
|
|
|
* be optional, like in HTTP/1.1 */
|
|
|
|
request_st *r = NULL;
|
|
|
|
for (uint32_t i = 0, rused = h2c->rused; i < rused; ++i) {
|
|
|
|
request_st * const rr = h2c->r[i];
|
|
|
|
if (rr->h2id != id) continue;
|
|
|
|
r = rr;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (NULL == r) {
|
|
|
|
h2_send_goaway_e(con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
if (r->h2state != H2_STATE_OPEN
|
|
|
|
&& r->h2state != H2_STATE_HALF_CLOSED_LOCAL) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_STREAM_CLOSED);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/* RFC 7540 is not explicit in restricting HEADERS (trailers) following
|
|
|
|
* (optional) DATA frames, but in following HTTP/1.1, we limit to single
|
|
|
|
* (optional) HEADERS (+ CONTINUATIONs) after (optional) DATA frame(s)
|
|
|
|
* and require that the HEADERS frame set END_STREAM flag. */
|
|
|
|
if (!(flags & H2_FLAG_END_STREAM)) {
|
|
|
|
h2_send_rst_stream(r, con, H2_E_PROTOCOL_ERROR);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return h2_recv_end_data(r, con, 0) ? r : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
h2_parse_headers_frame (request_st * const restrict r, const unsigned char *psrc, const uint32_t plen, const int trailers)
|
|
|
|
{
|
|
|
|
h2con * const h2c = r->con->h2;
|
|
|
|
struct lshpack_dec * const restrict decoder = &h2c->decoder;
|
|
|
|
const unsigned char * const endp = psrc + plen;
|
|
|
|
http_header_parse_ctx hpctx;
|
|
|
|
hpctx.hlen = 0;
|
|
|
|
hpctx.pseudo = 1;
|
|
|
|
hpctx.scheme = 0;
|
|
|
|
hpctx.trailers = trailers;
|
|
|
|
hpctx.max_request_field_size = r->conf.max_request_field_size;
|
|
|
|
hpctx.http_parseopts = r->conf.http_parseopts;
|
|
|
|
const int log_request_header = r->conf.log_request_header;
|
|
|
|
int rc = LSHPACK_OK;
|
|
|
|
/*buffer_clear(&r->target);*//*(initial state)*/
|
|
|
|
|
|
|
|
/*(h2_init_con() resized h2r->tmp_buf to 64k; shared with r->tmp_buf)*/
|
|
|
|
buffer * const tb = r->tmp_buf;
|
|
|
|
force_assert(tb->size >= 65536);/*(sanity check; remove in future)*/
|
|
|
|
const lsxpack_strlen_t tbsz = (tb->size <= LSXPACK_MAX_STRLEN)
|
|
|
|
? tb->size
|
|
|
|
: LSXPACK_MAX_STRLEN;
|
|
|
|
|
|
|
|
/* note: #define LSHPACK_DEC_HTTP1X_OUTPUT 1 (default) configures
|
|
|
|
* decoder to produce output in format: "field-name: value\r\n"
|
|
|
|
* future: modify build system to define value to 0 in lshpack.h
|
|
|
|
* against which lighttpd builds (or define value in build systems)
|
|
|
|
* Then adjust code below to not use the HTTP/1.x compatibility,
|
|
|
|
* as it is less efficient to copy into HTTP/1.1 request and reparse
|
|
|
|
* than it is to directly parse each decoded header line. */
|
|
|
|
lsxpack_header_t lsx;
|
|
|
|
while (psrc < endp) {
|
|
|
|
memset(&lsx, 0, sizeof(lsxpack_header_t));
|
|
|
|
lsx.buf = tb->ptr;
|
|
|
|
lsx.val_len = tbsz;
|
|
|
|
rc = lshpack_dec_decode(decoder, &psrc, endp, &lsx);
|
|
|
|
if (0 == lsx.name_len)
|
|
|
|
rc = LSHPACK_ERR_BAD_DATA;
|
|
|
|
if (rc == LSHPACK_OK) {
|
|
|
|
hpctx.k = lsx.buf+lsx.name_offset;
|
|
|
|
hpctx.v = lsx.buf+lsx.val_offset;
|
|
|
|
hpctx.klen = lsx.name_len;
|
|
|
|
hpctx.vlen = lsx.val_len;
|
|
|
|
|
|
|
|
if (log_request_header)
|
|
|
|
log_error(r->conf.errh, __FILE__, __LINE__,
|
|
|
|
"fd:%d id:%u rqst: %.*s: %.*s", r->con->fd, r->h2id,
|
|
|
|
(int)hpctx.klen, hpctx.k, (int)hpctx.vlen, hpctx.v);
|
|
|
|
|
|
|
|
r->http_status = http_request_parse_header(r, &hpctx);
|
|
|
|
if (0 != r->http_status)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#if 0 /*(see catch-all below)*/
|
|
|
|
/* Send GOAWAY (further below) (decoder state not maintained on error)
|
|
|
|
* (see comments above why decoder state must be maintained) */
|
|
|
|
/* XXX: future: could try to send :status 431 here
|
|
|
|
* and reset other active streams in H2_STATE_OPEN */
|
|
|
|
else if (rc == LSHPACK_ERR_MORE_BUF) {
|
|
|
|
/* XXX: TODO if (r->conf.log_request_header_on_error) */
|
|
|
|
r->http_status = 431; /* Request Header Fields Too Large */
|
|
|
|
/*(try to avoid reading/buffering more data for this request)*/
|
|
|
|
r->h2_rwin = 0; /*(out-of-sync with peer, but is error case)*/
|
|
|
|
/*r->h2state = H2_STATE_HALF_CLOSED_REMOTE*/
|
|
|
|
/* psrc was not advanced if LSHPACK_ERR_MORE_BUF;
|
|
|
|
* processing must stop (since not retrying w/ larger buf)*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
else { /* LSHPACK_ERR_BAD_DATA */
|
|
|
|
/* GOAWAY with H2_E_PROTOCOL_ERROR is not specific enough
|
|
|
|
* to tell peer to not retry request, so send RST_STREAM
|
|
|
|
* (slightly more specific, but not by much) before GOAWAY*/
|
|
|
|
/* LSHPACK_ERR_MORE_BUF is treated as an attack, send GOAWAY
|
|
|
|
* (h2r->tmp_buf was resized to 64k in h2_init_con()) */
|
|
|
|
request_h2error_t err = H2_E_COMPRESSION_ERROR;
|
|
|
|
if (rc != LSHPACK_ERR_BAD_DATA) {
|
|
|
|
/* LSHPACK_ERR_TOO_LARGE, LSHPACK_ERR_MORE_BUF */
|
|
|
|
err = H2_E_PROTOCOL_ERROR;
|
|
|
|
h2_send_rst_stream(r, r->con, err);
|
|
|
|
}
|
|
|
|
if (!h2c->sent_goaway && !trailers)
|
|
|
|
h2c->h2_cid = r->h2id;
|
|
|
|
h2_send_goaway_e(r->con, err);
|
|
|
|
if (!trailers) {
|
|
|
|
h2_retire_stream(r, r->con);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
r->state = CON_STATE_ERROR;
|
|
|
|
r->h2state = H2_STATE_CLOSED;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|