Imported Upstream version 7.59.0
[platform/upstream/curl.git] / lib / http2.c
index af69c72..b2c34e9 100644 (file)
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
 #include "http2.h"
 #include "http.h"
 #include "sendf.h"
+#include "select.h"
 #include "curl_base64.h"
 #include "strcase.h"
 #include "multiif.h"
-#include "conncache.h"
 #include "url.h"
 #include "connect.h"
 #include "strtoofft.h"
 
 #define HTTP2_HUGE_WINDOW_SIZE (1 << 30)
 
+#ifdef DEBUG_HTTP2
+#define H2BUGF(x) x
+#else
+#define H2BUGF(x) do { } WHILE_FALSE
+#endif
+
 /*
  * Curl_http2_init_state() is called when the easy handle is created and
  * allows for HTTP/2 specific init of state.
@@ -140,17 +146,60 @@ static CURLcode http2_disconnect(struct connectdata *conn,
   struct http_conn *c = &conn->proto.httpc;
   (void)dead_connection;
 
-  DEBUGF(infof(conn->data, "HTTP/2 DISCONNECT starts now\n"));
+  H2BUGF(infof(conn->data, "HTTP/2 DISCONNECT starts now\n"));
 
   nghttp2_session_del(c->h2);
   Curl_safefree(c->inbuf);
   http2_stream_free(conn->data->req.protop);
 
-  DEBUGF(infof(conn->data, "HTTP/2 DISCONNECT done\n"));
+  H2BUGF(infof(conn->data, "HTTP/2 DISCONNECT done\n"));
 
   return CURLE_OK;
 }
 
+/*
+ * The server may send us data at any point (e.g. PING frames). Therefore,
+ * we cannot assume that an HTTP/2 socket is dead just because it is readable.
+ *
+ * Instead, if it is readable, run Curl_connalive() to peek at the socket
+ * and distinguish between closed and data.
+ */
+static bool http2_connisdead(struct connectdata *check)
+{
+  int sval;
+  bool ret_val = TRUE;
+
+  sval = SOCKET_READABLE(check->sock[FIRSTSOCKET], 0);
+  if(sval == 0) {
+    /* timeout */
+    ret_val = FALSE;
+  }
+  else if(sval & CURL_CSELECT_ERR) {
+    /* socket is in an error state */
+    ret_val = TRUE;
+  }
+  else if(sval & CURL_CSELECT_IN) {
+    /* readable with no error. could still be closed */
+    ret_val = !Curl_connalive(check);
+  }
+
+  return ret_val;
+}
+
+
+static unsigned int http2_conncheck(struct connectdata *check,
+                                    unsigned int checks_to_perform)
+{
+  unsigned int ret_val = CONNRESULT_NONE;
+
+  if(checks_to_perform & CONNCHECK_ISDEAD) {
+    if(http2_connisdead(check))
+      ret_val |= CONNRESULT_DEAD;
+  }
+
+  return ret_val;
+}
+
 /* called from Curl_http_setup_conn */
 void Curl_http2_setup_req(struct Curl_easy *data)
 {
@@ -161,11 +210,10 @@ void Curl_http2_setup_req(struct Curl_easy *data)
   http->status_code = -1;
   http->pausedata = NULL;
   http->pauselen = 0;
-  http->error_code = NGHTTP2_NO_ERROR;
   http->closed = FALSE;
   http->close_handled = FALSE;
   http->mem = data->state.buffer;
-  http->len = BUFSIZE;
+  http->len = data->set.buffer_size;
   http->memlen = 0;
 }
 
@@ -174,6 +222,7 @@ void Curl_http2_setup_conn(struct connectdata *conn)
 {
   conn->proto.httpc.settings.max_concurrent_streams =
     DEFAULT_MAX_CONCURRENT_STREAMS;
+  conn->proto.httpc.error_code = NGHTTP2_NO_ERROR;
 }
 
 /*
@@ -181,7 +230,7 @@ void Curl_http2_setup_conn(struct connectdata *conn)
  * but will be used at run-time when the protocol is dynamically switched from
  * HTTP to HTTP2.
  */
-const struct Curl_handler Curl_handler_http2 = {
+static const struct Curl_handler Curl_handler_http2 = {
   "HTTP",                               /* scheme */
   ZERO_NULL,                            /* setup_connection */
   Curl_http,                            /* do_it */
@@ -196,12 +245,13 @@ const struct Curl_handler Curl_handler_http2 = {
   http2_perform_getsock,                /* perform_getsock */
   http2_disconnect,                     /* disconnect */
   ZERO_NULL,                            /* readwrite */
+  http2_conncheck,                      /* connection_check */
   PORT_HTTP,                            /* defport */
   CURLPROTO_HTTP,                       /* protocol */
   PROTOPT_STREAM                        /* flags */
 };
 
-const struct Curl_handler Curl_handler_http2_ssl = {
+static const struct Curl_handler Curl_handler_http2_ssl = {
   "HTTPS",                              /* scheme */
   ZERO_NULL,                            /* setup_connection */
   Curl_http,                            /* do_it */
@@ -216,6 +266,7 @@ const struct Curl_handler Curl_handler_http2_ssl = {
   http2_perform_getsock,                /* perform_getsock */
   http2_disconnect,                     /* disconnect */
   ZERO_NULL,                            /* readwrite */
+  http2_conncheck,                      /* connection_check */
   PORT_HTTP,                            /* defport */
   CURLPROTO_HTTPS,                      /* protocol */
   PROTOPT_SSL | PROTOPT_STREAM          /* flags */
@@ -338,12 +389,12 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
     struct HTTP *stream = h->data->req.protop;
     size_t len = strlen(header);
     size_t i;
-    for(i=0; i<stream->push_headers_used; i++) {
+    for(i = 0; i<stream->push_headers_used; i++) {
       if(!strncmp(header, stream->push_headers[i], len)) {
         /* sub-match, make sure that it is followed by a colon */
         if(stream->push_headers[i][len] != ':')
           continue;
-        return &stream->push_headers[i][len+1];
+        return &stream->push_headers[i][len + 1];
       }
     }
   }
@@ -383,7 +434,7 @@ static int push_promise(struct Curl_easy *data,
                         const nghttp2_push_promise *frame)
 {
   int rv;
-  DEBUGF(infof(data, "PUSH_PROMISE received, stream %u!\n",
+  H2BUGF(infof(data, "PUSH_PROMISE received, stream %u!\n",
                frame->promised_stream_id));
   if(data->multi->push_cb) {
     struct HTTP *stream;
@@ -403,21 +454,24 @@ static int push_promise(struct Curl_easy *data,
     heads.data = data;
     heads.frame = frame;
     /* ask the application */
-    DEBUGF(infof(data, "Got PUSH_PROMISE, ask application!\n"));
+    H2BUGF(infof(data, "Got PUSH_PROMISE, ask application!\n"));
 
     stream = data->req.protop;
     if(!stream) {
       failf(data, "Internal NULL stream!\n");
+      (void)Curl_close(newhandle);
       rv = 1;
       goto fail;
     }
 
+    Curl_set_in_callback(data, true);
     rv = data->multi->push_cb(data, newhandle,
                               stream->push_headers_used, &heads,
                               data->multi->push_userp);
+    Curl_set_in_callback(data, false);
 
     /* free the headers again */
-    for(i=0; i<stream->push_headers_used; i++)
+    for(i = 0; i<stream->push_headers_used; i++)
       free(stream->push_headers[i]);
     free(stream->push_headers);
     stream->push_headers = NULL;
@@ -451,7 +505,7 @@ static int push_promise(struct Curl_easy *data,
                                          frame->promised_stream_id, newhandle);
   }
   else {
-    DEBUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n"));
+    H2BUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n"));
     rv = 1;
   }
   fail:
@@ -474,21 +528,22 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
     /* stream ID zero is for connection-oriented stuff */
     if(frame->hd.type == NGHTTP2_SETTINGS) {
       uint32_t max_conn = httpc->settings.max_concurrent_streams;
-      DEBUGF(infof(conn->data, "Got SETTINGS\n"));
+      H2BUGF(infof(conn->data, "Got SETTINGS\n"));
       httpc->settings.max_concurrent_streams =
         nghttp2_session_get_remote_settings(
           session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
       httpc->settings.enable_push =
         nghttp2_session_get_remote_settings(
           session, NGHTTP2_SETTINGS_ENABLE_PUSH);
-      DEBUGF(infof(conn->data, "MAX_CONCURRENT_STREAMS == %d\n",
+      H2BUGF(infof(conn->data, "MAX_CONCURRENT_STREAMS == %d\n",
                    httpc->settings.max_concurrent_streams));
-      DEBUGF(infof(conn->data, "ENABLE_PUSH == %s\n",
+      H2BUGF(infof(conn->data, "ENABLE_PUSH == %s\n",
                    httpc->settings.enable_push?"TRUE":"false"));
       if(max_conn != httpc->settings.max_concurrent_streams) {
         /* only signal change if the value actually changed */
         infof(conn->data,
-              "Connection state changed (MAX_CONCURRENT_STREAMS updated)!\n");
+              "Connection state changed (MAX_CONCURRENT_STREAMS == %d)!\n",
+              httpc->settings.max_concurrent_streams);
         Curl_multi_connchanged(conn->data->multi);
       }
     }
@@ -499,7 +554,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
     lastStream = stream_id;
   }
   if(!data_s) {
-    DEBUGF(infof(conn->data,
+    H2BUGF(infof(conn->data,
                  "No Curl_easy associated with stream: %x\n",
                  stream_id));
     return 0;
@@ -507,12 +562,12 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
 
   stream = data_s->req.protop;
   if(!stream) {
-    DEBUGF(infof(conn->data, "No proto pointer for stream: %x\n",
+    H2BUGF(infof(conn->data, "No proto pointer for stream: %x\n",
                  stream_id));
     return NGHTTP2_ERR_CALLBACK_FAILURE;
   }
 
-  DEBUGF(infof(data_s, "on_frame_recv() header %x stream %x\n",
+  H2BUGF(infof(data_s, "on_frame_recv() header %x stream %x\n",
                frame->hd.type, stream_id));
 
   switch(frame->hd.type) {
@@ -554,7 +609,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
            ncopy);
     stream->nread_header_recvbuf += ncopy;
 
-    DEBUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p\n",
+    H2BUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p\n",
                  ncopy, stream_id, stream->mem));
 
     stream->len -= ncopy;
@@ -568,7 +623,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
 
       /* if we receive data for another handle, wake that up */
       if(conn_s->data != data_s)
-        Curl_expire(data_s, 0);
+        Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
     }
     break;
   case NGHTTP2_PUSH_PROMISE:
@@ -583,7 +638,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
     }
     break;
   default:
-    DEBUGF(infof(conn->data, "Got frame type %x for stream %u!\n",
+    H2BUGF(infof(conn->data, "Got frame type %x for stream %u!\n",
                  frame->hd.type, stream_id));
     break;
   }
@@ -596,10 +651,13 @@ static int on_invalid_frame_recv(nghttp2_session *session,
 {
   struct Curl_easy *data_s = NULL;
   (void)userp;
+#if !defined(DEBUG_HTTP2) || defined(CURL_DISABLE_VERBOSE_STRINGS)
+  (void)lib_error_code;
+#endif
 
   data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
   if(data_s) {
-    DEBUGF(infof(data_s,
+    H2BUGF(infof(data_s,
                  "on_invalid_frame_recv() was called, error=%d:%s\n",
                  lib_error_code, nghttp2_strerror(lib_error_code)));
   }
@@ -642,9 +700,9 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
 
   /* if we receive data for another handle, wake that up */
   if(conn->data != data_s)
-    Curl_expire(data_s, 0);
+    Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
 
-  DEBUGF(infof(data_s, "%zu data received for stream %u "
+  H2BUGF(infof(data_s, "%zu data received for stream %u "
                "(%zu left in buffer %p, total %zu)\n",
                nread, stream_id,
                stream->len, stream->mem,
@@ -653,7 +711,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
   if(nread < len) {
     stream->pausedata = data + nread;
     stream->pauselen = len - nread;
-    DEBUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer"
+    H2BUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer"
                  ", stream %u\n",
                  len - nread, stream_id));
     data_s->easy_conn->proto.httpc.pause_stream_id = stream_id;
@@ -681,7 +739,7 @@ static int before_frame_send(nghttp2_session *session,
 
   data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
   if(data_s) {
-    DEBUGF(infof(data_s, "before_frame_send() was called\n"));
+    H2BUGF(infof(data_s, "before_frame_send() was called\n"));
   }
 
   return 0;
@@ -695,7 +753,7 @@ static int on_frame_send(nghttp2_session *session,
 
   data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
   if(data_s) {
-    DEBUGF(infof(data_s, "on_frame_send() was called, length = %zd\n",
+    H2BUGF(infof(data_s, "on_frame_send() was called, length = %zd\n",
                  frame->hd.length));
   }
   return 0;
@@ -706,10 +764,13 @@ static int on_frame_not_send(nghttp2_session *session,
 {
   struct Curl_easy *data_s;
   (void)userp;
+#if !defined(DEBUG_HTTP2) || defined(CURL_DISABLE_VERBOSE_STRINGS)
+  (void)lib_error_code;
+#endif
 
   data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
   if(data_s) {
-    DEBUGF(infof(data_s,
+    H2BUGF(infof(data_s,
                  "on_frame_not_send() was called, lib_error_code = %d\n",
                  lib_error_code));
   }
@@ -725,6 +786,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
   (void)stream_id;
 
   if(stream_id) {
+    struct http_conn *httpc;
     /* get the stream from the hash based on Stream ID, stream ID zero is for
        connection-oriented stuff */
     data_s = nghttp2_session_get_stream_user_data(session, stream_id);
@@ -733,20 +795,21 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
          decided to reject stream (e.g., PUSH_PROMISE). */
       return 0;
     }
-    DEBUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u\n",
+    H2BUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u\n",
                  Curl_http2_strerror(error_code), error_code, stream_id));
     stream = data_s->req.protop;
     if(!stream)
       return NGHTTP2_ERR_CALLBACK_FAILURE;
 
-    stream->error_code = error_code;
     stream->closed = TRUE;
     data_s->state.drain++;
-    conn->proto.httpc.drain_total++;
+    httpc = &conn->proto.httpc;
+    httpc->drain_total++;
+    httpc->error_code = error_code;
 
     /* remove the entry from the hash as the stream is now gone */
     nghttp2_session_set_stream_user_data(session, stream_id, 0);
-    DEBUGF(infof(data_s, "Removed stream %u hash!\n", stream_id));
+    H2BUGF(infof(data_s, "Removed stream %u hash!\n", stream_id));
   }
   return 0;
 }
@@ -763,7 +826,7 @@ static int on_begin_headers(nghttp2_session *session,
     return 0;
   }
 
-  DEBUGF(infof(data_s, "on_begin_headers() was called\n"));
+  H2BUGF(infof(data_s, "on_begin_headers() was called\n"));
 
   if(frame->hd.type != NGHTTP2_HEADERS) {
     return 0;
@@ -775,9 +838,9 @@ static int on_begin_headers(nghttp2_session *session,
   }
 
   /* This is trailer HEADERS started.  Allocate buffer for them. */
-  DEBUGF(infof(data_s, "trailer field started\n"));
+  H2BUGF(infof(data_s, "trailer field started\n"));
 
-  assert(stream->trailer_recvbuf == NULL);
+  DEBUGASSERT(stream->trailer_recvbuf == NULL);
 
   stream->trailer_recvbuf = Curl_add_buffer_init();
   if(!stream->trailer_recvbuf) {
@@ -873,10 +936,10 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
 
   if(stream->bodystarted) {
     /* This is trailer fields. */
-    /* 3 is for ":" and "\r\n". */
-    uint32_t n = (uint32_t)(namelen + valuelen + 3);
+    /* 4 is for ": " and "\r\n". */
+    uint32_t n = (uint32_t)(namelen + valuelen + 4);
 
-    DEBUGF(infof(data_s, "h2 trailer: %.*s: %.*s\n", namelen, name, valuelen,
+    H2BUGF(infof(data_s, "h2 trailer: %.*s: %.*s\n", namelen, name, valuelen,
                  value));
 
     Curl_add_buffer(stream->trailer_recvbuf, &n, sizeof(n));
@@ -902,9 +965,9 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
     Curl_add_buffer(stream->header_recvbuf, " \r\n", 3);
     /* if we receive data for another handle, wake that up */
     if(conn->data != data_s)
-      Curl_expire(data_s, 0);
+      Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
 
-    DEBUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)\n",
+    H2BUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)\n",
                  stream->status_code, data_s));
     return 0;
   }
@@ -918,9 +981,9 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
   Curl_add_buffer(stream->header_recvbuf, "\r\n", 2);
   /* if we receive data for another handle, wake that up */
   if(conn->data != data_s)
-    Curl_expire(data_s, 0);
+    Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
 
-  DEBUGF(infof(data_s, "h2 header: %.*s: %.*s\n", namelen, name, valuelen,
+  H2BUGF(infof(data_s, "h2 header: %.*s: %.*s\n", namelen, name, valuelen,
                value));
 
   return 0; /* 0 is successful */
@@ -969,7 +1032,7 @@ static ssize_t data_source_read_callback(nghttp2_session *session,
   else if(nread == 0)
     return NGHTTP2_ERR_DEFERRED;
 
-  DEBUGF(infof(data_s, "data_source_read_callback: "
+  H2BUGF(infof(data_s, "data_source_read_callback: "
                "returns %zu bytes stream %u\n",
                nread, stream_id));
 
@@ -1015,7 +1078,7 @@ void Curl_http2_done(struct connectdata *conn, bool premature)
   struct http_conn *httpc = &conn->proto.httpc;
 
   if(http->header_recvbuf) {
-    DEBUGF(infof(data, "free header_recvbuf!!\n"));
+    H2BUGF(infof(data, "free header_recvbuf!!\n"));
     Curl_add_buffer_free(http->header_recvbuf);
     http->header_recvbuf = NULL; /* clear the pointer */
     Curl_add_buffer_free(http->trailer_recvbuf);
@@ -1131,14 +1194,17 @@ CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req,
                                          httpc->local_settings_num);
   if(!binlen) {
     failf(conn->data, "nghttp2 unexpectedly failed on pack_settings_payload");
+    Curl_add_buffer_free(req);
     return CURLE_FAILED_INIT;
   }
   conn->proto.httpc.binlen = binlen;
 
   result = Curl_base64url_encode(conn->data, (const char *)binsettings, binlen,
                                  &base64, &blen);
-  if(result)
+  if(result) {
+    Curl_add_buffer_free(req);
     return result;
+  }
 
   result = Curl_add_bufferf(req,
                             "Connection: Upgrade, HTTP2-Settings\r\n"
@@ -1170,13 +1236,14 @@ static int h2_session_send(struct Curl_easy *data,
  * This function returns 0 if it succeeds, or -1 and error code will
  * be assigned to *err.
  */
-static int h2_process_pending_input(struct Curl_easy *data,
+static int h2_process_pending_input(struct connectdata *conn,
                                     struct http_conn *httpc,
                                     CURLcode *err)
 {
   ssize_t nread;
   char *inbuf;
   ssize_t rv;
+  struct Curl_easy *data = conn->data;
 
   nread = httpc->inbuflen - httpc->nread_inbuf;
   inbuf = httpc->inbuf + httpc->nread_inbuf;
@@ -1191,7 +1258,7 @@ static int h2_process_pending_input(struct Curl_easy *data,
   }
 
   if(nread == rv) {
-    DEBUGF(infof(data,
+    H2BUGF(infof(data,
                  "h2_process_pending_input: All data in connection buffer "
                  "processed\n"));
     httpc->inbuflen = 0;
@@ -1199,7 +1266,7 @@ static int h2_process_pending_input(struct Curl_easy *data,
   }
   else {
     httpc->nread_inbuf += rv;
-    DEBUGF(infof(data,
+    H2BUGF(infof(data,
                  "h2_process_pending_input: %zu bytes left in connection "
                  "buffer\n",
                  httpc->inbuflen - httpc->nread_inbuf));
@@ -1212,9 +1279,15 @@ static int h2_process_pending_input(struct Curl_easy *data,
   }
 
   if(should_close_session(httpc)) {
-    DEBUGF(infof(data,
+    H2BUGF(infof(data,
                  "h2_process_pending_input: nothing to do in this session\n"));
-    *err = CURLE_HTTP2;
+    if(httpc->error_code)
+      *err = CURLE_HTTP2;
+    else {
+      /* not an error per se, but should still close the connection */
+      connclose(conn, "GOAWAY received");
+      *err = CURLE_OK;
+    }
     return -1;
   }
 
@@ -1245,7 +1318,7 @@ CURLcode Curl_http2_done_sending(struct connectdata *conn)
          that it can signal EOF to nghttp2 */
       (void)nghttp2_session_resume_data(h2, stream->stream_id);
 
-      (void)h2_process_pending_input(conn->data, httpc, &result);
+      (void)h2_process_pending_input(conn, httpc, &result);
     }
   }
   return result;
@@ -1269,7 +1342,7 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
   data->state.drain = 0;
 
   if(httpc->pause_stream_id == 0) {
-    if(h2_process_pending_input(data, httpc, err) != 0) {
+    if(h2_process_pending_input(conn, httpc, err) != 0) {
       return -1;
     }
   }
@@ -1278,10 +1351,10 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
 
   /* Reset to FALSE to prevent infinite loop in readwrite_data function. */
   stream->closed = FALSE;
-  if(stream->error_code != NGHTTP2_NO_ERROR) {
+  if(httpc->error_code != NGHTTP2_NO_ERROR) {
     failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %d)",
-          stream->stream_id, Curl_http2_strerror(stream->error_code),
-          stream->error_code);
+          stream->stream_id, Curl_http2_strerror(httpc->error_code),
+          httpc->error_code);
     *err = CURLE_HTTP2_STREAM;
     return -1;
   }
@@ -1315,7 +1388,7 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
 
   stream->close_handled = TRUE;
 
-  DEBUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close\n"));
+  H2BUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close\n"));
   return 0;
 }
 
@@ -1356,7 +1429,7 @@ static int h2_session_send(struct Curl_easy *data,
 
     h2_pri_spec(data, &pri_spec);
 
-    DEBUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)\n",
+    H2BUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)\n",
                  stream->stream_id, data));
     rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id,
                                  &pri_spec);
@@ -1380,7 +1453,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
   (void)sockindex; /* we always do HTTP2 on sockindex 0 */
 
   if(should_close_session(httpc)) {
-    DEBUGF(infof(data,
+    H2BUGF(infof(data,
                  "http2_recv: nothing to do in this session\n"));
     *err = CURLE_HTTP2;
     return -1;
@@ -1406,16 +1479,16 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
            ncopy);
     stream->nread_header_recvbuf += ncopy;
 
-    DEBUGF(infof(data, "http2_recv: Got %d bytes from header_recvbuf\n",
+    H2BUGF(infof(data, "http2_recv: Got %d bytes from header_recvbuf\n",
                  (int)ncopy));
     return ncopy;
   }
 
-  DEBUGF(infof(data, "http2_recv: easy %p (stream %u)\n",
+  H2BUGF(infof(data, "http2_recv: easy %p (stream %u)\n",
                data, stream->stream_id));
 
   if((data->state.drain) && stream->memlen) {
-    DEBUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u!! (%p => %p)\n",
+    H2BUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u!! (%p => %p)\n",
                  stream->memlen, stream->stream_id,
                  stream->mem, mem));
     if(mem != stream->mem) {
@@ -1429,7 +1502,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
       /* We have paused nghttp2, but we have no pause data (see
          on_data_chunk_recv). */
       httpc->pause_stream_id = 0;
-      if(h2_process_pending_input(data, httpc, &result) != 0) {
+      if(h2_process_pending_input(conn, httpc, &result) != 0) {
         *err = result;
         return -1;
       }
@@ -1445,8 +1518,8 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
 
     infof(data, "%zu data bytes written\n", nread);
     if(stream->pauselen == 0) {
-      DEBUGF(infof(data, "Unpaused by stream %u\n", stream->stream_id));
-      assert(httpc->pause_stream_id == stream->stream_id);
+      H2BUGF(infof(data, "Unpaused by stream %u\n", stream->stream_id));
+      DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
       httpc->pause_stream_id = 0;
 
       stream->pausedata = NULL;
@@ -1459,12 +1532,12 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
          frames, then we have to call it again with 0-length data.
          Without this, on_stream_close callback will not be called,
          and stream could be hanged. */
-      if(h2_process_pending_input(data, httpc, &result) != 0) {
+      if(h2_process_pending_input(conn, httpc, &result) != 0) {
         *err = result;
         return -1;
       }
     }
-    DEBUGF(infof(data, "http2_recv: returns unpaused %zd bytes on stream %u\n",
+    H2BUGF(infof(data, "http2_recv: returns unpaused %zd bytes on stream %u\n",
                  nread, stream->stream_id));
     return nread;
   }
@@ -1477,7 +1550,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
        socket is not read.  But it seems that usually streams are
        notified with its drain property, and socket is read again
        quickly. */
-    DEBUGF(infof(data, "stream %x is paused, pause id: %x\n",
+    H2BUGF(infof(data, "stream %x is paused, pause id: %x\n",
                  stream->stream_id, httpc->pause_stream_id));
     *err = CURLE_AGAIN;
     return -1;
@@ -1511,7 +1584,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
         return -1;
       }
 
-      DEBUGF(infof(data, "nread=%zd\n", nread));
+      H2BUGF(infof(data, "nread=%zd\n", nread));
 
       httpc->inbuflen = nread;
       inbuf = httpc->inbuf;
@@ -1520,7 +1593,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
       nread = httpc->inbuflen - httpc->nread_inbuf;
       inbuf = httpc->inbuf + httpc->nread_inbuf;
 
-      DEBUGF(infof(data, "Use data left in connection buffer, nread=%zd\n",
+      H2BUGF(infof(data, "Use data left in connection buffer, nread=%zd\n",
                    nread));
     }
     rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread);
@@ -1529,17 +1602,17 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
       failf(data, "nghttp2_session_mem_recv() returned %d:%s\n",
             rv, nghttp2_strerror((int)rv));
       *err = CURLE_RECV_ERROR;
-      return 0;
+      return -1;
     }
-    DEBUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", rv));
+    H2BUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", rv));
     if(nread == rv) {
-      DEBUGF(infof(data, "All data in connection buffer processed\n"));
+      H2BUGF(infof(data, "All data in connection buffer processed\n"));
       httpc->inbuflen = 0;
       httpc->nread_inbuf = 0;
     }
     else {
       httpc->nread_inbuf += rv;
-      DEBUGF(infof(data, "%zu bytes left in connection buffer\n",
+      H2BUGF(infof(data, "%zu bytes left in connection buffer\n",
                    httpc->inbuflen - httpc->nread_inbuf));
     }
     /* Always send pending frames in nghttp2 session, because
@@ -1547,25 +1620,25 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
     rv = h2_session_send(data, httpc->h2);
     if(rv != 0) {
       *err = CURLE_SEND_ERROR;
-      return 0;
+      return -1;
     }
 
     if(should_close_session(httpc)) {
-      DEBUGF(infof(data, "http2_recv: nothing to do in this session\n"));
+      H2BUGF(infof(data, "http2_recv: nothing to do in this session\n"));
       *err = CURLE_HTTP2;
       return -1;
     }
   }
   if(stream->memlen) {
     ssize_t retlen = stream->memlen;
-    DEBUGF(infof(data, "http2_recv: returns %zd for stream %u\n",
+    H2BUGF(infof(data, "http2_recv: returns %zd for stream %u\n",
                  retlen, stream->stream_id));
     stream->memlen = 0;
 
     if(httpc->pause_stream_id == stream->stream_id) {
       /* data for this stream is returned now, but this stream caused a pause
          already so we need it called again asap */
-      DEBUGF(infof(data, "Data returned for PAUSED stream %u\n",
+      H2BUGF(infof(data, "Data returned for PAUSED stream %u\n",
                    stream->stream_id));
     }
     else if(!stream->closed) {
@@ -1582,7 +1655,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
     return http2_handle_stream_close(conn, data, stream, err);
   }
   *err = CURLE_AGAIN;
-  DEBUGF(infof(data, "http2_recv returns AGAIN for stream %u\n",
+  H2BUGF(infof(data, "http2_recv returns AGAIN for stream %u\n",
                stream->stream_id));
   return -1;
 }
@@ -1684,7 +1757,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
 
   (void)sockindex;
 
-  DEBUGF(infof(conn->data, "http2_send len=%zu\n", len));
+  H2BUGF(infof(conn->data, "http2_send len=%zu\n", len));
 
   if(stream->stream_id != -1) {
     if(stream->close_handled) {
@@ -1713,7 +1786,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
     stream->upload_len = 0;
 
     if(should_close_session(httpc)) {
-      DEBUGF(infof(conn->data, "http2_send: nothing to do in this session\n"));
+      H2BUGF(infof(conn->data, "http2_send: nothing to do in this session\n"));
       *err = CURLE_HTTP2;
       return -1;
     }
@@ -1726,7 +1799,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
       nghttp2_session_resume_data(h2, stream->stream_id);
     }
 
-    DEBUGF(infof(conn->data, "http2_send returns %zu for stream %u\n", len,
+    H2BUGF(infof(conn->data, "http2_send returns %zu for stream %u\n", len,
                  stream->stream_id));
     return len;
   }
@@ -1793,9 +1866,6 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
     goto fail;
   }
 
-  hdbuf = end + 1;
-
-  end = line_end;
   nva[2].name = (unsigned char *)":scheme";
   nva[2].namelen = strlen((char *)nva[2].name);
   if(conn->handler->flags & PROTOPT_SSL)
@@ -1885,7 +1955,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
     for(i = 0; i < nheader; ++i) {
       acc += nva[i].namelen + nva[i].valuelen;
 
-      DEBUGF(infof(conn->data, "h2 header: %.*s:%.*s\n",
+      H2BUGF(infof(conn->data, "h2 header: %.*s:%.*s\n",
                    nva[i].namelen, nva[i].name,
                    nva[i].valuelen, nva[i].value));
     }
@@ -1902,6 +1972,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
   switch(conn->data->set.httpreq) {
   case HTTPREQ_POST:
   case HTTPREQ_POST_FORM:
+  case HTTPREQ_POST_MIME:
   case HTTPREQ_PUT:
     if(conn->data->state.infilesize != -1)
       stream->upload_left = conn->data->state.infilesize;
@@ -1922,7 +1993,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
   Curl_safefree(nva);
 
   if(stream_id < 0) {
-    DEBUGF(infof(conn->data, "http2_send() send error\n"));
+    H2BUGF(infof(conn->data, "http2_send() send error\n"));
     *err = CURLE_SEND_ERROR;
     return -1;
   }
@@ -1941,7 +2012,7 @@ static ssize_t http2_send(struct connectdata *conn, int sockindex,
   }
 
   if(should_close_session(httpc)) {
-    DEBUGF(infof(conn->data, "http2_send: nothing to do in this session\n"));
+    H2BUGF(infof(conn->data, "http2_send: nothing to do in this session\n"));
     *err = CURLE_HTTP2;
     return -1;
   }
@@ -2099,7 +2170,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
     return CURLE_HTTP2;
   }
 
-  DEBUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", nproc));
+  H2BUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n", nproc));
 
   if((ssize_t)nread == nproc) {
     httpc->inbuflen = 0;
@@ -2119,7 +2190,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
   }
 
   if(should_close_session(httpc)) {
-    DEBUGF(infof(data,
+    H2BUGF(infof(data,
                  "nghttp2_session_send(): nothing to do in this session\n"));
     return CURLE_HTTP2;
   }
@@ -2127,40 +2198,46 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
   return CURLE_OK;
 }
 
-void Curl_http2_add_child(struct Curl_easy *parent, struct Curl_easy *child,
-                          bool exclusive)
+CURLcode Curl_http2_add_child(struct Curl_easy *parent,
+                              struct Curl_easy *child,
+                              bool exclusive)
 {
-  struct Curl_http2_dep **tail;
-  struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep));
-  dep->data = child;
-
-  if(parent->set.stream_dependents && exclusive) {
-    struct Curl_http2_dep *node = parent->set.stream_dependents;
-    while(node) {
-      node->data->set.stream_depends_on = child;
-      node = node->next;
+  if(parent) {
+    struct Curl_http2_dep **tail;
+    struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep));
+    if(!dep)
+      return CURLE_OUT_OF_MEMORY;
+    dep->data = child;
+
+    if(parent->set.stream_dependents && exclusive) {
+      struct Curl_http2_dep *node = parent->set.stream_dependents;
+      while(node) {
+        node->data->set.stream_depends_on = child;
+        node = node->next;
+      }
+
+      tail = &child->set.stream_dependents;
+      while(*tail)
+        tail = &(*tail)->next;
+
+      DEBUGASSERT(!*tail);
+      *tail = parent->set.stream_dependents;
+      parent->set.stream_dependents = 0;
     }
 
-    tail = &child->set.stream_dependents;
-    while(*tail)
+    tail = &parent->set.stream_dependents;
+    while(*tail) {
+      (*tail)->data->set.stream_depends_e = FALSE;
       tail = &(*tail)->next;
+    }
 
     DEBUGASSERT(!*tail);
-    *tail = parent->set.stream_dependents;
-    parent->set.stream_dependents = 0;
-  }
-
-  tail = &parent->set.stream_dependents;
-  while(*tail) {
-    (*tail)->data->set.stream_depends_e = FALSE;
-    tail = &(*tail)->next;
+    *tail = dep;
   }
 
-  DEBUGASSERT(!*tail);
-  *tail = dep;
-
   child->set.stream_depends_on = parent;
   child->set.stream_depends_e = exclusive;
+  return CURLE_OK;
 }
 
 void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child)