]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: fix sending of large requests
authorStefan Eissing <stefan@eissing.org>
Tue, 5 Sep 2023 11:44:13 +0000 (13:44 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 5 Sep 2023 14:36:03 +0000 (16:36 +0200)
- refs #11342 where errors with git https interactions
  were observed
- problem was caused by 1st sends of size larger than 64KB
  which resulted in later retries of 64KB only
- limit sending of 1st block to 64KB
- adjust h2/h3 filters to cope with parsing the HTTP/1.1
  formatted request in chunks

- introducing Curl_nwrite() as companion to Curl_write()
  for the many cases where the sockindex is already known

Fixes #11342 (again)
Closes #11803

13 files changed:
lib/gopher.c
lib/http.c
lib/http2.c
lib/krb5.c
lib/mqtt.c
lib/pingpong.c
lib/sendf.c
lib/sendf.h
lib/smb.c
lib/telnet.c
lib/vquic/curl_ngtcp2.c
lib/vquic/curl_quiche.c
tests/http/test_07_upload.py

index 4a11d9364e7a7ebaad335b32ec1c1de609ede719..61e41b7e477649a3856bcd336924ca497937616a 100644 (file)
@@ -185,7 +185,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done)
     if(strlen(sel) < 1)
       break;
 
-    result = Curl_write(data, sockfd, sel, k, &amount);
+    result = Curl_nwrite(data, FIRSTSOCKET, sel, k, &amount);
     if(!result) { /* Which may not have written it all! */
       result = Curl_client_write(data, CLIENTWRITE_HEADER, sel, amount);
       if(result)
@@ -227,7 +227,7 @@ static CURLcode gopher_do(struct Curl_easy *data, bool *done)
   free(sel_org);
 
   if(!result)
-    result = Curl_write(data, sockfd, "\r\n", 2, &amount);
+    result = Curl_nwrite(data, FIRSTSOCKET, "\r\n", 2, &amount);
   if(result) {
     failf(data, "Failed sending Gopher request");
     return result;
index e4a0372727c7557300aed98318428f6a51724783..26aac873d15c4b45739242b5e016eb5c6a326ee5 100644 (file)
@@ -1281,7 +1281,7 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
                           curl_off_t *bytes_written,
                           /* how much of the buffer contains body data */
                           curl_off_t included_body_bytes,
-                          int socketindex)
+                          int sockindex)
 {
   ssize_t amount;
   CURLcode result;
@@ -1289,12 +1289,9 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
   size_t size;
   struct connectdata *conn = data->conn;
   size_t sendsize;
-  curl_socket_t sockfd;
   size_t headersize;
 
-  DEBUGASSERT(socketindex <= SECONDARYSOCKET);
-
-  sockfd = Curl_conn_get_socket(data, socketindex);
+  DEBUGASSERT(sockindex <= SECONDARYSOCKET && sockindex >= 0);
 
   /* The looping below is required since we use non-blocking sockets, but due
      to the circumstances we will just loop and try again and again etc */
@@ -1376,9 +1373,25 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
       else
         sendsize = size;
     }
+
+    /* We currently cannot send more that this for http here:
+     * - if sending blocks, it return 0 as amount
+     * - we then whisk aside the `in` into the `http` struct
+     *   and install our own `data->state.fread_func` that
+     *   on subsequent calls reads `in` empty.
+     * - when the whisked away `in` is empty, the `fread_func`
+     *   is restored ot its original state.
+     * The problem is that `fread_func` can only return
+     * `upload_buffer_size` lengths. If the send we do here
+     * is larger and blocks, we do re-sending with smaller
+     * amounts of data and connection filters do not like
+     * that.
+     */
+    if(http && (sendsize > (size_t)data->set.upload_buffer_size))
+      sendsize = (size_t)data->set.upload_buffer_size;
   }
 
-  result = Curl_write(data, sockfd, ptr, sendsize, &amount);
+  result = Curl_nwrite(data, sockindex, ptr, sendsize, &amount);
 
   if(!result) {
     /*
index 903eac4c4cb3b129202f8c8f18f9e8b7ee409c04..e0cda76d287003a818ebc379010738ff6258e6f8 100644 (file)
@@ -41,6 +41,7 @@
 #include "urlapi-int.h"
 #include "cfilters.h"
 #include "connect.h"
+#include "rand.h"
 #include "strtoofft.h"
 #include "strdup.h"
 #include "transfer.h"
@@ -175,6 +176,7 @@ struct stream_ctx {
   int32_t id; /* HTTP/2 protocol identifier for stream */
   struct bufq recvbuf; /* response buffer */
   struct bufq sendbuf; /* request buffer */
+  struct h1_req_parser h1; /* parsing the request */
   struct dynhds resp_trailers; /* response trailer fields */
   size_t resp_hds_len; /* amount of response header bytes in recvbuf */
   size_t upload_blocked_len;
@@ -253,6 +255,7 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf,
                   H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
   Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
                   H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
+  Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
   Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
   stream->resp_hds_len = 0;
   stream->bodystarted = FALSE;
@@ -313,6 +316,7 @@ static void http2_data_done(struct Curl_cfilter *cf,
 
   Curl_bufq_free(&stream->sendbuf);
   Curl_bufq_free(&stream->recvbuf);
+  Curl_h1_req_parse_free(&stream->h1);
   Curl_dynhds_free(&stream->resp_trailers);
   if(stream->push_headers) {
     /* if they weren't used and then freed before */
@@ -2014,7 +2018,6 @@ static ssize_t h2_submit(struct stream_ctx **pstream,
 {
   struct cf_h2_ctx *ctx = cf->ctx;
   struct stream_ctx *stream = NULL;
-  struct h1_req_parser h1;
   struct dynhds h2_headers;
   nghttp2_nv *nva = NULL;
   const void *body = NULL;
@@ -2024,7 +2027,6 @@ static ssize_t h2_submit(struct stream_ctx **pstream,
   nghttp2_priority_spec pri_spec;
   ssize_t nwritten;
 
-  Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
   Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
 
   *err = http2_data_setup(cf, data, &stream);
@@ -2033,17 +2035,22 @@ static ssize_t h2_submit(struct stream_ctx **pstream,
     goto out;
   }
 
-  nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
+  nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
   if(nwritten < 0)
     goto out;
-  DEBUGASSERT(h1.done);
-  DEBUGASSERT(h1.req);
+  if(!stream->h1.done) {
+    /* need more data */
+    goto out;
+  }
+  DEBUGASSERT(stream->h1.req);
 
-  *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
+  *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
   if(*err) {
     nwritten = -1;
     goto out;
   }
+  /* no longer needed */
+  Curl_h1_req_parse_free(&stream->h1);
 
   nheader = Curl_dynhds_count(&h2_headers);
   nva = malloc(sizeof(nghttp2_nv) * nheader);
@@ -2151,7 +2158,6 @@ out:
               stream? stream->id : -1, nwritten, *err);
   Curl_safefree(nva);
   *pstream = stream;
-  Curl_h1_req_parse_free(&h1);
   Curl_dynhds_free(&h2_headers);
   return nwritten;
 }
@@ -2179,7 +2185,8 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
       if(len < stream->upload_blocked_len) {
         /* Did we get called again with a smaller `len`? This should not
          * happen. We are not prepared to handle that. */
-        failf(data, "HTTP/2 send again with decreased length");
+        failf(data, "HTTP/2 send again with decreased length (%zd vs %zd)",
+              len, stream->upload_blocked_len);
         *err = CURLE_HTTP2;
         nwritten = -1;
         goto out;
@@ -2211,11 +2218,8 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
        * optionally request body, and now are going to send or sending
        * more request body in DATA frame */
       nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err);
-      if(nwritten < 0) {
-        if(*err != CURLE_AGAIN)
-          goto out;
-        nwritten = 0;
-      }
+      if(nwritten < 0 && *err != CURLE_AGAIN)
+        goto out;
     }
 
     if(!Curl_bufq_is_empty(&stream->sendbuf)) {
@@ -2262,7 +2266,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
     blocked = 1;
   }
 
-  if(stream && blocked) {
+  if(stream && blocked && nwritten > 0) {
     /* Unable to send all data, due to connection blocked or H2 window
      * exhaustion. Data is left in our stream buffer, or nghttp2's internal
      * frame buffer or our network out buffer. */
index 80e4015cea59647b03601b9b7d48b008d8434bff..07278e77745c30a0ce69803b7d99c016541b66b1 100644 (file)
@@ -91,7 +91,7 @@ static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn,
 #ifdef HAVE_GSSAPI
     conn->data_prot = PROT_CMD;
 #endif
-    result = Curl_write(data, conn->sock[FIRSTSOCKET], sptr, write_len,
+    result = Curl_nwrite(data, FIRSTSOCKET, sptr, write_len,
                         &bytes_written);
 #ifdef HAVE_GSSAPI
     DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
index 799a21a568b4dc25dca698f18455cd6d5b1eb555..30edf3dd8a4f9a8f8eadb2be89a260b0a42e37ce 100644 (file)
@@ -117,11 +117,9 @@ static CURLcode mqtt_send(struct Curl_easy *data,
                           char *buf, size_t len)
 {
   CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
-  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
   struct MQTT *mq = data->req.p.mqtt;
   ssize_t n;
-  result = Curl_write(data, sockfd, buf, len, &n);
+  result = Curl_nwrite(data, FIRSTSOCKET, buf, len, &n);
   if(result)
     return result;
   Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n);
index 85fd75badc75e3a883423f7c81bb7e6354755cc8..136985fc920328119382d0d70c7a70f17b4c98af 100644 (file)
@@ -204,8 +204,7 @@ CURLcode Curl_pp_vsendf(struct Curl_easy *data,
 #ifdef HAVE_GSSAPI
   conn->data_prot = PROT_CMD;
 #endif
-  result = Curl_write(data, conn->sock[FIRSTSOCKET], s, write_len,
-                      &bytes_written);
+  result = Curl_nwrite(data, FIRSTSOCKET, s, write_len, &bytes_written);
   if(result)
     return result;
 #ifdef HAVE_GSSAPI
@@ -467,11 +466,10 @@ CURLcode Curl_pp_flushsend(struct Curl_easy *data,
                            struct pingpong *pp)
 {
   /* we have a piece of a command still left to send */
-  struct connectdata *conn = data->conn;
   ssize_t written;
-  curl_socket_t sock = conn->sock[FIRSTSOCKET];
-  CURLcode result = Curl_write(data, sock, pp->sendthis + pp->sendsize -
-                               pp->sendleft, pp->sendleft, &written);
+  CURLcode result = Curl_nwrite(data, FIRSTSOCKET,
+                                pp->sendthis + pp->sendsize - pp->sendleft,
+                                pp->sendleft, &written);
   if(result)
     return result;
 
index 798a223a140114180019ff0c91de011ef3ea33c0..d79ad08fa566c0b52904a99f5d3b359cdbaa49e3 100644 (file)
@@ -139,27 +139,27 @@ static size_t convert_lineends(struct Curl_easy *data,
 #endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */
 
 /*
- * Curl_write() is an internal write function that sends data to the
- * server. Works with plain sockets, SCP, SSL or kerberos.
+ * Curl_nwrite() is an internal write function that sends data to the
+ * server. Works with a socket index for the connection.
  *
- * If the write would block (CURLE_AGAIN), we return CURLE_OK and
- * (*written == 0). Otherwise we return regular CURLcode value.
+ * If the write would block (CURLE_AGAIN), it returns CURLE_OK and
+ * (*nwritten == 0). Otherwise we return regular CURLcode value.
  */
-CURLcode Curl_write(struct Curl_easy *data,
-                    curl_socket_t sockfd,
-                    const void *mem,
-                    size_t len,
-                    ssize_t *written)
+CURLcode Curl_nwrite(struct Curl_easy *data,
+                     int sockindex,
+                     const void *buf,
+                     size_t blen,
+                     ssize_t *pnwritten)
 {
-  ssize_t bytes_written;
+  ssize_t nwritten;
   CURLcode result = CURLE_OK;
   struct connectdata *conn;
-  int num;
+
+  DEBUGASSERT(sockindex >= 0 && sockindex < 2);
+  DEBUGASSERT(pnwritten);
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
   conn = data->conn;
-  num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]);
-
 #ifdef CURLDEBUG
   {
     /* Allow debug builds to override this logic to force short sends
@@ -168,31 +168,47 @@ CURLcode Curl_write(struct Curl_easy *data,
     if(p) {
       size_t altsize = (size_t)strtoul(p, NULL, 10);
       if(altsize)
-        len = CURLMIN(len, altsize);
+        blen = CURLMIN(blen, altsize);
     }
   }
 #endif
-  bytes_written = conn->send[num](data, num, mem, len, &result);
-
-  *written = bytes_written;
-  if(bytes_written >= 0)
-    /* we completely ignore the curlcode value when subzero is not returned */
-    return CURLE_OK;
+  nwritten = conn->send[sockindex](data, sockindex, buf, blen, &result);
+  if(result == CURLE_AGAIN) {
+    nwritten = 0;
+    result = CURLE_OK;
+  }
+  else if(result) {
+    nwritten = -1; /* make sure */
+  }
+  else {
+    DEBUGASSERT(nwritten >= 0);
+  }
 
-  /* handle CURLE_AGAIN or a send failure */
-  switch(result) {
-  case CURLE_AGAIN:
-    *written = 0;
-    return CURLE_OK;
+  *pnwritten = nwritten;
+  return result;
+}
 
-  case CURLE_OK:
-    /* general send failure */
-    return CURLE_SEND_ERROR;
+/*
+ * Curl_write() is an internal write function that sends data to the
+ * server. Works with plain sockets, SCP, SSL or kerberos.
+ *
+ * If the write would block (CURLE_AGAIN), we return CURLE_OK and
+ * (*written == 0). Otherwise we return regular CURLcode value.
+ */
+CURLcode Curl_write(struct Curl_easy *data,
+                    curl_socket_t sockfd,
+                    const void *mem,
+                    size_t len,
+                    ssize_t *written)
+{
+  struct connectdata *conn;
+  int num;
 
-  default:
-    /* we got a specific curlcode, forward it */
-    return result;
-  }
+  DEBUGASSERT(data);
+  DEBUGASSERT(data->conn);
+  conn = data->conn;
+  num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]);
+  return Curl_nwrite(data, num, mem, len, written);
 }
 
 static CURLcode pausewrite(struct Curl_easy *data,
index 25881efa8820c6613b6a3115e57f95dcd8211ba9..5ef2b91df774536b426d23c170f8db8f944472b4 100644 (file)
@@ -51,4 +51,11 @@ CURLcode Curl_write(struct Curl_easy *data,
                     const void *mem, size_t len,
                     ssize_t *written);
 
+/* internal write-function, using sockindex for connection destination */
+CURLcode Curl_nwrite(struct Curl_easy *data,
+                     int sockindex,
+                     const void *buf,
+                     size_t blen,
+                     ssize_t *pnwritten);
+
 #endif /* HEADER_CURL_SENDF_H */
index bc4e883551997a56e408a840686a41e8b37844e2..afcc99de245908b1e8aad45f2ac4aefb8868a943 100644 (file)
--- a/lib/smb.c
+++ b/lib/smb.c
@@ -564,12 +564,11 @@ static CURLcode smb_send(struct Curl_easy *data, ssize_t len,
                          size_t upload_size)
 {
   struct connectdata *conn = data->conn;
-  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
   struct smb_conn *smbc = &conn->proto.smbc;
   ssize_t bytes_written;
   CURLcode result;
 
-  result = Curl_write(data, sockfd, data->state.ulbuf,
+  result = Curl_nwrite(data, FIRSTSOCKET, data->state.ulbuf,
                       len, &bytes_written);
   if(result)
     return result;
@@ -587,7 +586,6 @@ static CURLcode smb_send(struct Curl_easy *data, ssize_t len,
 static CURLcode smb_flush(struct Curl_easy *data)
 {
   struct connectdata *conn = data->conn;
-  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
   struct smb_conn *smbc = &conn->proto.smbc;
   ssize_t bytes_written;
   ssize_t len = smbc->send_size - smbc->sent;
@@ -596,9 +594,9 @@ static CURLcode smb_flush(struct Curl_easy *data)
   if(!smbc->send_size)
     return CURLE_OK;
 
-  result = Curl_write(data, sockfd,
-                      data->state.ulbuf + smbc->sent,
-                      len, &bytes_written);
+  result = Curl_nwrite(data, FIRSTSOCKET,
+                       data->state.ulbuf + smbc->sent,
+                       len, &bytes_written);
   if(result)
     return result;
 
index 1d7a592e78db18905cfffe021352d421ef21ded9..850f88c1eccb6720eeba3358aeb1ef8b294a1b90 100644 (file)
@@ -1266,10 +1266,10 @@ static CURLcode send_telnet_data(struct Curl_easy *data,
         break;
       default:                    /* write! */
         bytes_written = 0;
-        result = Curl_write(data, conn->sock[FIRSTSOCKET],
-                            outbuf + total_written,
-                            outlen - total_written,
-                            &bytes_written);
+        result = Curl_nwrite(data, FIRSTSOCKET,
+                             outbuf + total_written,
+                             outlen - total_written,
+                             &bytes_written);
         total_written += bytes_written;
         break;
     }
index 7e71be98757e39c45586e3c0de446e113860f850..e75041067a95067e248f8687a08b35a8385cc6d3 100644 (file)
@@ -179,6 +179,7 @@ struct h3_stream_ctx {
   int64_t id; /* HTTP/3 protocol identifier */
   struct bufq sendbuf;   /* h3 request body */
   struct bufq recvbuf;   /* h3 response body */
+  struct h1_req_parser h1; /* h1 request parsing */
   size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */
   size_t upload_blocked_len; /* the amount written last and EGAINed */
   size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */
@@ -226,6 +227,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf,
   Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
                   H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
   stream->recv_buf_nonflow = 0;
+  Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
 
   H3_STREAM_LCTX(data) = stream;
   return CURLE_OK;
@@ -240,6 +242,7 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
     CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id);
     Curl_bufq_free(&stream->sendbuf);
     Curl_bufq_free(&stream->recvbuf);
+    Curl_h1_req_parse_free(&stream->h1);
     free(stream);
     H3_STREAM_LCTX(data) = NULL;
   }
@@ -1625,7 +1628,6 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf,
 {
   struct cf_ngtcp2_ctx *ctx = cf->ctx;
   struct h3_stream_ctx *stream = NULL;
-  struct h1_req_parser h1;
   struct dynhds h2_headers;
   size_t nheader;
   nghttp3_nv *nva = NULL;
@@ -1635,7 +1637,6 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf,
   nghttp3_data_reader reader;
   nghttp3_data_reader *preader = NULL;
 
-  Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
   Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
 
   *err = h3_data_setup(cf, data);
@@ -1651,17 +1652,22 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf,
     goto out;
   }
 
-  nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
+  nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
   if(nwritten < 0)
     goto out;
-  DEBUGASSERT(h1.done);
-  DEBUGASSERT(h1.req);
+  if(!stream->h1.done) {
+    /* need more data */
+    goto out;
+  }
+  DEBUGASSERT(stream->h1.req);
 
-  *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
+  *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
   if(*err) {
     nwritten = -1;
     goto out;
   }
+  /* no longer needed */
+  Curl_h1_req_parse_free(&stream->h1);
 
   nheader = Curl_dynhds_count(&h2_headers);
   nva = malloc(sizeof(nghttp3_nv) * nheader);
@@ -1734,7 +1740,6 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf,
 
 out:
   free(nva);
-  Curl_h1_req_parse_free(&h1);
   Curl_dynhds_free(&h2_headers);
   return nwritten;
 }
index 849a948230b9277c52040b0ba25d8310d5a95348..b65bea871c44c717903ed8a6c8b9768acdef26d1 100644 (file)
@@ -210,6 +210,7 @@ static CURLcode quic_ssl_setup(struct Curl_cfilter *cf, struct Curl_easy *data)
 struct stream_ctx {
   int64_t id; /* HTTP/3 protocol stream identifier */
   struct bufq recvbuf; /* h3 response */
+  struct h1_req_parser h1; /* h1 request parsing */
   uint64_t error3; /* HTTP/3 stream error code */
   curl_off_t upload_left; /* number of request bytes left to upload */
   bool closed; /* TRUE on stream close */
@@ -298,6 +299,7 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf,
   stream->id = -1;
   Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
                   H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
+  Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
   return CURLE_OK;
 }
 
@@ -314,6 +316,7 @@ static void h3_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
       --ctx->sends_on_hold;
     }
     Curl_bufq_free(&stream->recvbuf);
+    Curl_h1_req_parse_free(&stream->h1);
     free(stream);
     H3_STREAM_LCTX(data) = NULL;
   }
@@ -936,7 +939,6 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf,
   struct stream_ctx *stream = H3_STREAM_CTX(data);
   size_t nheader, i;
   int64_t stream3_id;
-  struct h1_req_parser h1;
   struct dynhds h2_headers;
   quiche_h3_header *nva = NULL;
   ssize_t nwritten;
@@ -950,21 +952,25 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf,
     DEBUGASSERT(stream);
   }
 
-  Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
   Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
 
   DEBUGASSERT(stream);
-  nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
+  nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
   if(nwritten < 0)
     goto out;
-  DEBUGASSERT(h1.done);
-  DEBUGASSERT(h1.req);
+  if(!stream->h1.done) {
+    /* need more data */
+    goto out;
+  }
+  DEBUGASSERT(stream->h1.req);
 
-  *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
+  *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
   if(*err) {
     nwritten = -1;
     goto out;
   }
+  /* no longer needed */
+  Curl_h1_req_parse_free(&stream->h1);
 
   nheader = Curl_dynhds_count(&h2_headers);
   nva = malloc(sizeof(quiche_h3_header) * nheader);
@@ -1041,7 +1047,6 @@ static ssize_t h3_open_stream(struct Curl_cfilter *cf,
 
 out:
   free(nva);
-  Curl_h1_req_parse_free(&h1);
   Curl_dynhds_free(&h2_headers);
   return nwritten;
 }
index 2738ed3409461f3d014831fe435419b4bf92a5c4..5aee3ea266a8f10671ed75f008e970a67231d4ec 100644 (file)
@@ -407,6 +407,32 @@ class TestUpload:
         respdata = open(curl.response_file(0)).readlines()
         assert respdata == indata
 
+    # POST data urlencoded, small enough to be sent with request headers
+    # and request headers are so large that the first send is larger
+    # than our default upload buffer length (64KB).
+    # Unfixed, this will fail when run with CURL_DBG_SOCK_WBLOCK=80 most
+    # of the time
+    @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+    def test_07_41_post_urlenc_small(self, env: Env, httpd, nghttpx, repeat, proto):
+        if proto == 'h3' and not env.have_h3():
+            pytest.skip("h3 not supported")
+        if proto == 'h3' and env.curl_uses_lib('msh3'):
+            pytest.skip("msh3 fails here")
+        if proto == 'h3' and env.curl_uses_lib('quiche'):
+            pytest.skip("quiche has CWND issues with large requests")
+        fdata = os.path.join(env.gen_dir, 'data-63k')
+        curl = CurlClient(env=env)
+        extra_args = ['--trace-config', 'http/2,http/3']
+        # add enough headers so that the first send chunk is > 64KB
+        for i in range(63):
+            extra_args.extend(['-H', f'x{i:02d}: {"y"*1019}'])
+        url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
+        r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=extra_args)
+        r.check_stats(count=1, http_status=200, exitcode=0)
+        indata = open(fdata).readlines()
+        respdata = open(curl.response_file(0)).readlines()
+        assert respdata == indata
+
     def check_download(self, count, srcfile, curl):
         for i in range(count):
             dfile = curl.download_file(i)