mirror of
https://github.com/moparisthebest/curl
synced 2024-12-22 16:18:48 -05:00
HTTP2: Support expect: 100-continue
"Expect: 100-continue", which was once deprecated in HTTP/2, is now resurrected in HTTP/2 draft 14. This change adds its support to HTTP/2 code. This change also includes stricter header field checking.
This commit is contained in:
parent
e4f6adb023
commit
595f5f0e43
@ -1525,10 +1525,6 @@ static CURLcode expect100(struct SessionHandle *data,
|
|||||||
const char *ptr;
|
const char *ptr;
|
||||||
data->state.expect100header = FALSE; /* default to false unless it is set
|
data->state.expect100header = FALSE; /* default to false unless it is set
|
||||||
to TRUE below */
|
to TRUE below */
|
||||||
if(conn->httpversion == 20) {
|
|
||||||
/* We don't use Expect in HTTP2 */
|
|
||||||
return CURLE_OK;
|
|
||||||
}
|
|
||||||
if(use_http_1_1plus(data, conn)) {
|
if(use_http_1_1plus(data, conn)) {
|
||||||
/* if not doing HTTP 1.0 or disabled explicitly, we add a Expect:
|
/* if not doing HTTP 1.0 or disabled explicitly, we add a Expect:
|
||||||
100-continue to the headers which actually speeds up post operations
|
100-continue to the headers which actually speeds up post operations
|
||||||
|
@ -169,7 +169,9 @@ struct http_conn {
|
|||||||
sending send_underlying; /* underlying send Curl_send callback */
|
sending send_underlying; /* underlying send Curl_send callback */
|
||||||
recving recv_underlying; /* underlying recv Curl_recv callback */
|
recving recv_underlying; /* underlying recv Curl_recv callback */
|
||||||
bool closed; /* TRUE on HTTP2 stream close */
|
bool closed; /* TRUE on HTTP2 stream close */
|
||||||
Curl_send_buffer *header_recvbuf; /* store response headers */
|
Curl_send_buffer *header_recvbuf; /* store response headers. We
|
||||||
|
store non-final and final
|
||||||
|
response headers into it. */
|
||||||
size_t nread_header_recvbuf; /* number of bytes in header_recvbuf
|
size_t nread_header_recvbuf; /* number of bytes in header_recvbuf
|
||||||
fed into upper layer */
|
fed into upper layer */
|
||||||
int32_t stream_id; /* stream we are interested in */
|
int32_t stream_id; /* stream we are interested in */
|
||||||
@ -185,6 +187,7 @@ struct http_conn {
|
|||||||
const uint8_t *upload_mem; /* points to a buffer to read from */
|
const uint8_t *upload_mem; /* points to a buffer to read from */
|
||||||
size_t upload_len; /* size of the buffer 'upload_mem' points to */
|
size_t upload_len; /* size of the buffer 'upload_mem' points to */
|
||||||
size_t upload_left; /* number of bytes left to upload */
|
size_t upload_left; /* number of bytes left to upload */
|
||||||
|
int status_code; /* HTTP status code */
|
||||||
#else
|
#else
|
||||||
int unused; /* prevent a compiler warning */
|
int unused; /* prevent a compiler warning */
|
||||||
#endif
|
#endif
|
||||||
|
154
lib/http2.c
154
lib/http2.c
@ -191,23 +191,75 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|||||||
struct connectdata *conn = (struct connectdata *)userp;
|
struct connectdata *conn = (struct connectdata *)userp;
|
||||||
struct http_conn *c = &conn->proto.httpc;
|
struct http_conn *c = &conn->proto.httpc;
|
||||||
int rv;
|
int rv;
|
||||||
|
size_t left, ncopy;
|
||||||
|
|
||||||
(void)session;
|
(void)session;
|
||||||
(void)frame;
|
(void)frame;
|
||||||
infof(conn->data, "on_frame_recv() was called with header %x\n",
|
infof(conn->data, "on_frame_recv() was called with header %x\n",
|
||||||
frame->hd.type);
|
frame->hd.type);
|
||||||
switch(frame->hd.type) {
|
switch(frame->hd.type) {
|
||||||
case NGHTTP2_HEADERS:
|
case NGHTTP2_DATA:
|
||||||
if(frame->headers.cat != NGHTTP2_HCAT_RESPONSE)
|
/* If body started, then receiving DATA is illegal. */
|
||||||
|
if(!c->bodystarted) {
|
||||||
|
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
|
||||||
|
if(nghttp2_is_fatal(rv)) {
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
case NGHTTP2_HEADERS:
|
||||||
|
if(frame->headers.cat == NGHTTP2_HCAT_REQUEST)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if(c->bodystarted) {
|
||||||
|
/* Only valid HEADERS after body started is trailer header,
|
||||||
|
which is not fully supported in this code. If HEADERS is not
|
||||||
|
trailer, then it is a PROTOCOL_ERROR. */
|
||||||
|
if((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
|
||||||
|
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
|
||||||
|
if(nghttp2_is_fatal(rv)) {
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(c->status_code == -1) {
|
||||||
|
/* No :status header field means PROTOCOL_ERROR. */
|
||||||
|
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
|
||||||
|
if(nghttp2_is_fatal(rv)) {
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Only final status code signals the end of header */
|
||||||
|
if(c->status_code / 100 != 1) {
|
||||||
c->bodystarted = TRUE;
|
c->bodystarted = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->status_code = -1;
|
||||||
|
|
||||||
Curl_add_buffer(c->header_recvbuf, "\r\n", 2);
|
Curl_add_buffer(c->header_recvbuf, "\r\n", 2);
|
||||||
c->nread_header_recvbuf = c->len < c->header_recvbuf->size_used ?
|
|
||||||
c->len : c->header_recvbuf->size_used;
|
|
||||||
|
|
||||||
memcpy(c->mem, c->header_recvbuf->buffer, c->nread_header_recvbuf);
|
left = c->header_recvbuf->size_used - c->nread_header_recvbuf;
|
||||||
|
ncopy = c->len < left ? c->len : left;
|
||||||
|
|
||||||
c->mem += c->nread_header_recvbuf;
|
memcpy(c->mem, c->header_recvbuf->buffer + c->nread_header_recvbuf, ncopy);
|
||||||
c->len -= c->nread_header_recvbuf;
|
c->nread_header_recvbuf += ncopy;
|
||||||
|
|
||||||
|
c->mem += ncopy;
|
||||||
|
c->len -= ncopy;
|
||||||
break;
|
break;
|
||||||
case NGHTTP2_PUSH_PROMISE:
|
case NGHTTP2_PUSH_PROMISE:
|
||||||
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
@ -339,6 +391,33 @@ static int on_begin_headers(nghttp2_session *session,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Decode HTTP status code. Returns -1 if no valid status code was
|
||||||
|
decoded. */
|
||||||
|
static int decode_status_code(const uint8_t *value, size_t len)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if(len != 3) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = 0;
|
||||||
|
|
||||||
|
for(i = 0; i < 3; ++i) {
|
||||||
|
char c = value[i];
|
||||||
|
|
||||||
|
if(c < '0' || c > '9') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
res *= 10;
|
||||||
|
res += c - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
static const char STATUS[] = ":status";
|
static const char STATUS[] = ":status";
|
||||||
|
|
||||||
/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */
|
/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */
|
||||||
@ -350,6 +429,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|||||||
{
|
{
|
||||||
struct connectdata *conn = (struct connectdata *)userp;
|
struct connectdata *conn = (struct connectdata *)userp;
|
||||||
struct http_conn *c = &conn->proto.httpc;
|
struct http_conn *c = &conn->proto.httpc;
|
||||||
|
int rv;
|
||||||
|
|
||||||
(void)session;
|
(void)session;
|
||||||
(void)frame;
|
(void)frame;
|
||||||
(void)flags;
|
(void)flags;
|
||||||
@ -358,13 +439,64 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(c->bodystarted) {
|
||||||
|
/* Ignore trailer or HEADERS not mapped to HTTP semantics. The
|
||||||
|
consequence is handled in on_frame_recv(). */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!nghttp2_check_header_name(name, namelen) ||
|
||||||
|
!nghttp2_check_header_value(value, valuelen)) {
|
||||||
|
|
||||||
|
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
|
||||||
|
if(nghttp2_is_fatal(rv)) {
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
if(namelen == sizeof(":status") - 1 &&
|
if(namelen == sizeof(":status") - 1 &&
|
||||||
memcmp(STATUS, name, namelen) == 0) {
|
memcmp(STATUS, name, namelen) == 0) {
|
||||||
snprintf(c->header_recvbuf->buffer, 13, "HTTP/2.0 %s", value);
|
|
||||||
c->header_recvbuf->buffer[12] = '\r';
|
/* :status must appear exactly once. */
|
||||||
|
if(c->status_code != -1 ||
|
||||||
|
(c->status_code = decode_status_code(value, valuelen)) == -1) {
|
||||||
|
|
||||||
|
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
if(nghttp2_is_fatal(rv)) {
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Curl_add_buffer(c->header_recvbuf, "HTTP/2.0 ", 9);
|
||||||
|
Curl_add_buffer(c->header_recvbuf, value, valuelen);
|
||||||
|
Curl_add_buffer(c->header_recvbuf, "\r\n", 2);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
/* Here we are sure that namelen > 0 because of
|
||||||
|
nghttp2_check_header_name(). Pseudo header other than :status
|
||||||
|
is illegal. */
|
||||||
|
if(c->status_code == -1 || name[0] == ':') {
|
||||||
|
rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
||||||
|
frame->hd.stream_id,
|
||||||
|
NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
if(nghttp2_is_fatal(rv)) {
|
||||||
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
/* convert to a HTTP1-style header */
|
/* convert to a HTTP1-style header */
|
||||||
infof(conn->data, "got header\n");
|
infof(conn->data, "got header\n");
|
||||||
Curl_add_buffer(c->header_recvbuf, name, namelen);
|
Curl_add_buffer(c->header_recvbuf, name, namelen);
|
||||||
@ -803,11 +935,11 @@ CURLcode Curl_http2_setup(struct connectdata *conn)
|
|||||||
httpc->upload_mem = NULL;
|
httpc->upload_mem = NULL;
|
||||||
httpc->upload_len = 0;
|
httpc->upload_len = 0;
|
||||||
httpc->stream_id = -1;
|
httpc->stream_id = -1;
|
||||||
|
httpc->status_code = -1;
|
||||||
|
|
||||||
conn->httpversion = 20;
|
conn->httpversion = 20;
|
||||||
|
|
||||||
/* Put place holder for status line */
|
return 0;
|
||||||
return Curl_add_buffer(httpc->header_recvbuf, "HTTP/2.0 200\r\n", 14);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CURLcode Curl_http2_switched(struct connectdata *conn)
|
CURLcode Curl_http2_switched(struct connectdata *conn)
|
||||||
|
Loading…
Reference in New Issue
Block a user