|  | @@ -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))
 |