|
|
@@ -514,7 +514,7 @@ static int push_promise(struct Curl_easy *data,
|
|
|
struct connectdata *conn,
|
|
|
const nghttp2_push_promise *frame)
|
|
|
{
|
|
|
- int rv;
|
|
|
+ int rv; /* one of the CURL_PUSH_* defines */
|
|
|
H2BUGF(infof(data, "PUSH_PROMISE received, stream %u!\n",
|
|
|
frame->promised_stream_id));
|
|
|
if(data->multi->push_cb) {
|
|
|
@@ -528,7 +528,7 @@ static int push_promise(struct Curl_easy *data,
|
|
|
struct Curl_easy *newhandle = duphandle(data);
|
|
|
if(!newhandle) {
|
|
|
infof(data, "failed to duplicate handle\n");
|
|
|
- rv = 1; /* FAIL HARD */
|
|
|
+ rv = CURL_PUSH_DENY; /* FAIL HARD */
|
|
|
goto fail;
|
|
|
}
|
|
|
|
|
|
@@ -541,13 +541,15 @@ static int push_promise(struct Curl_easy *data,
|
|
|
if(!stream) {
|
|
|
failf(data, "Internal NULL stream!\n");
|
|
|
(void)Curl_close(&newhandle);
|
|
|
- rv = 1;
|
|
|
+ rv = CURL_PUSH_DENY;
|
|
|
goto fail;
|
|
|
}
|
|
|
|
|
|
rv = set_transfer_url(newhandle, &heads);
|
|
|
- if(rv)
|
|
|
+ if(rv) {
|
|
|
+ rv = CURL_PUSH_DENY;
|
|
|
goto fail;
|
|
|
+ }
|
|
|
|
|
|
Curl_set_in_callback(data, true);
|
|
|
rv = data->multi->push_cb(data, newhandle,
|
|
|
@@ -563,6 +565,7 @@ static int push_promise(struct Curl_easy *data,
|
|
|
stream->push_headers_used = 0;
|
|
|
|
|
|
if(rv) {
|
|
|
+ DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
|
|
|
/* denied, kill off the new handle again */
|
|
|
http2_stream_free(newhandle->req.protop);
|
|
|
newhandle->req.protop = NULL;
|
|
|
@@ -583,7 +586,7 @@ static int push_promise(struct Curl_easy *data,
|
|
|
http2_stream_free(newhandle->req.protop);
|
|
|
newhandle->req.protop = NULL;
|
|
|
Curl_close(&newhandle);
|
|
|
- rv = 1;
|
|
|
+ rv = CURL_PUSH_DENY;
|
|
|
goto fail;
|
|
|
}
|
|
|
|
|
|
@@ -595,12 +598,13 @@ static int push_promise(struct Curl_easy *data,
|
|
|
infof(data, "failed to set user_data for stream %d\n",
|
|
|
frame->promised_stream_id);
|
|
|
DEBUGASSERT(0);
|
|
|
+ rv = CURL_PUSH_DENY;
|
|
|
goto fail;
|
|
|
}
|
|
|
}
|
|
|
else {
|
|
|
H2BUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n"));
|
|
|
- rv = 1;
|
|
|
+ rv = CURL_PUSH_DENY;
|
|
|
}
|
|
|
fail:
|
|
|
return rv;
|
|
|
@@ -737,11 +741,16 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
case NGHTTP2_PUSH_PROMISE:
|
|
|
rv = push_promise(data_s, conn, &frame->push_promise);
|
|
|
if(rv) { /* deny! */
|
|
|
- rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
|
|
+ int h2;
|
|
|
+ DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
|
|
|
+ h2 = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
|
|
|
frame->push_promise.promised_stream_id,
|
|
|
NGHTTP2_CANCEL);
|
|
|
- if(nghttp2_is_fatal(rv)) {
|
|
|
- return rv;
|
|
|
+ if(nghttp2_is_fatal(h2))
|
|
|
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
+ else if(rv == CURL_PUSH_ERROROUT) {
|
|
|
+ DEBUGF(infof(data_s, "Fail the parent stream (too)\n"));
|
|
|
+ return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
}
|
|
|
}
|
|
|
break;
|
|
|
@@ -839,7 +848,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
|
|
|
return 0;
|
|
|
}
|
|
|
H2BUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u\n",
|
|
|
- nghttp2_strerror(error_code), error_code, stream_id));
|
|
|
+ nghttp2_http2_strerror(error_code), error_code, stream_id));
|
|
|
stream = data_s->req.protop;
|
|
|
if(!stream)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
@@ -1006,18 +1015,11 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
|
|
|
if(stream->bodystarted) {
|
|
|
/* This is a trailer */
|
|
|
- struct dynbuf trail;
|
|
|
H2BUGF(infof(data_s, "h2 trailer: %.*s: %.*s\n", namelen, name, valuelen,
|
|
|
value));
|
|
|
- Curl_dyn_init(&trail, DYN_H2_TRAILER);
|
|
|
- result = Curl_dyn_addf(&trail,
|
|
|
+ result = Curl_dyn_addf(&stream->trailer_recvbuf,
|
|
|
"%.*s: %.*s\r\n", namelen, name,
|
|
|
valuelen, value);
|
|
|
- if(!result)
|
|
|
- result = Curl_client_write(conn, CLIENTWRITE_HEADER,
|
|
|
- Curl_dyn_ptr(&trail),
|
|
|
- Curl_dyn_len(&trail));
|
|
|
- Curl_dyn_free(&trail);
|
|
|
if(result)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
|
|
@@ -1165,6 +1167,7 @@ void Curl_http2_done(struct Curl_easy *data, bool premature)
|
|
|
/* there might be allocated resources done before this got the 'h2' pointer
|
|
|
setup */
|
|
|
Curl_dyn_free(&http->header_recvbuf);
|
|
|
+ Curl_dyn_free(&http->trailer_recvbuf);
|
|
|
if(http->push_headers) {
|
|
|
/* if they weren't used and then freed before */
|
|
|
for(; http->push_headers_used > 0; --http->push_headers_used) {
|
|
|
@@ -1174,7 +1177,8 @@ void Curl_http2_done(struct Curl_easy *data, bool premature)
|
|
|
http->push_headers = NULL;
|
|
|
}
|
|
|
|
|
|
- if(!httpc->h2) /* not HTTP/2 ? */
|
|
|
+ if(!(data->conn->handler->protocol&PROTO_FAMILY_HTTP) ||
|
|
|
+ !httpc->h2) /* not HTTP/2 ? */
|
|
|
return;
|
|
|
|
|
|
if(premature) {
|
|
|
@@ -1203,6 +1207,13 @@ void Curl_http2_done(struct Curl_easy *data, bool premature)
|
|
|
}
|
|
|
http->stream_id = 0;
|
|
|
}
|
|
|
+
|
|
|
+ if(0 == nghttp2_session_check_request_allowed(httpc->h2)) {
|
|
|
+ /* No more requests are allowed in the current session, so the connection
|
|
|
+ may not be reused. This is set when a GOAWAY frame has been received or
|
|
|
+ when the limit of stream identifiers has been reached. */
|
|
|
+ connclose(data->conn, "http/2: No new requests allowed");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
@@ -1456,7 +1467,7 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
|
|
|
}
|
|
|
else if(httpc->error_code != NGHTTP2_NO_ERROR) {
|
|
|
failf(data, "HTTP/2 stream %d was not closed cleanly: %s (err %u)",
|
|
|
- stream->stream_id, nghttp2_strerror(httpc->error_code),
|
|
|
+ stream->stream_id, nghttp2_http2_strerror(httpc->error_code),
|
|
|
httpc->error_code);
|
|
|
*err = CURLE_HTTP2_STREAM;
|
|
|
return -1;
|
|
|
@@ -1470,6 +1481,31 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
+ if(Curl_dyn_len(&stream->trailer_recvbuf)) {
|
|
|
+ char *trailp = Curl_dyn_ptr(&stream->trailer_recvbuf);
|
|
|
+ char *lf;
|
|
|
+
|
|
|
+ do {
|
|
|
+ size_t len = 0;
|
|
|
+ CURLcode result;
|
|
|
+ /* each trailer line ends with a newline */
|
|
|
+ lf = strchr(trailp, '\n');
|
|
|
+ if(!lf)
|
|
|
+ break;
|
|
|
+ len = lf + 1 - trailp;
|
|
|
+
|
|
|
+ if(data->set.verbose)
|
|
|
+ Curl_debug(data, CURLINFO_HEADER_IN, trailp, len);
|
|
|
+ /* pass the trailers one by one to the callback */
|
|
|
+ result = Curl_client_write(conn, CLIENTWRITE_HEADER, trailp, len);
|
|
|
+ if(result) {
|
|
|
+ *err = result;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ trailp = ++lf;
|
|
|
+ } while(lf);
|
|
|
+ }
|
|
|
+
|
|
|
stream->close_handled = TRUE;
|
|
|
|
|
|
H2BUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close\n"));
|
|
|
@@ -2075,6 +2111,9 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
|
|
|
|
|
|
h2_pri_spec(conn->data, &pri_spec);
|
|
|
|
|
|
+ H2BUGF(infof(conn->data, "http2_send request allowed %d (easy handle %p)\n",
|
|
|
+ nghttp2_session_check_request_allowed(h2), (void *)conn->data));
|
|
|
+
|
|
|
switch(conn->data->state.httpreq) {
|
|
|
case HTTPREQ_POST:
|
|
|
case HTTPREQ_POST_FORM:
|
|
|
@@ -2151,6 +2190,7 @@ CURLcode Curl_http2_setup(struct connectdata *conn)
|
|
|
stream->stream_id = -1;
|
|
|
|
|
|
Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
|
|
|
+ Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
|
|
|
|
|
|
if((conn->handler == &Curl_handler_http2_ssl) ||
|
|
|
(conn->handler == &Curl_handler_http2))
|