summaryrefslogtreecommitdiff
path: root/net-misc/curl/files/curl-8.7.1-http2-git-clone.patch
blob: b07a3b0a8817fb31ff6e34c39fb0bf9571e5f08a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
https://bugs.gentoo.org/930633
https://github.com/curl/curl/issues/13474
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -187,6 +187,7 @@ struct h2_stream_ctx {
 
   int status_code; /* HTTP response status code */
   uint32_t error; /* stream error code */
+  CURLcode xfer_result; /* Result of writing out response */
   uint32_t local_window_size; /* the local recv window size */
   int32_t id; /* HTTP/2 protocol identifier for stream */
   BIT(resp_hds_complete); /* we have a complete, final response */
@@ -945,12 +946,39 @@ fail:
   return rv;
 }
 
-static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf,
+static void h2_xfer_write_resp_hd(struct Curl_cfilter *cf,
                                   struct Curl_easy *data,
-                                  const char *buf, size_t blen)
+                                  struct h2_stream_ctx *stream,
+                                  const char *buf, size_t blen, bool eos)
 {
-  (void)cf;
-  return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE);
+
+  /* If we already encountered an error, skip further writes */
+  if(!stream->xfer_result) {
+    stream->xfer_result = Curl_xfer_write_resp(data, (char *)buf, blen, eos);
+    if(stream->xfer_result)
+      CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of headers",
+                  stream->id, stream->xfer_result, blen);
+  }
+}
+
+static void h2_xfer_write_resp(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               struct h2_stream_ctx *stream,
+                               const char *buf, size_t blen, bool eos)
+{
+
+  /* If we already encountered an error, skip further writes */
+  if(!stream->xfer_result)
+    stream->xfer_result = Curl_xfer_write_resp(data, (char *)buf, blen, eos);
+  /* If the transfer write is errored, we do not want any more data */
+  if(stream->xfer_result) {
+    struct cf_h2_ctx *ctx = cf->ctx;
+    CURL_TRC_CF(data, cf, "[%d] error %d writing %zu bytes of data, "
+                "RST-ing stream",
+                stream->id, stream->xfer_result, blen);
+    nghttp2_submit_rst_stream(ctx->h2, 0, stream->id,
+                              NGHTTP2_ERR_CALLBACK_FAILURE);
+  }
 }
 
 static CURLcode on_stream_frame(struct Curl_cfilter *cf,
@@ -960,7 +988,6 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
   struct cf_h2_ctx *ctx = cf->ctx;
   struct h2_stream_ctx *stream = H2_STREAM_CTX(data);
   int32_t stream_id = frame->hd.stream_id;
-  CURLcode result;
   int rv;
 
   if(!stream) {
@@ -1008,9 +1035,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
       stream->status_code = -1;
     }
 
-    result = recvbuf_write_hds(cf, data, STRCONST("\r\n"));
-    if(result)
-      return result;
+    h2_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed);
 
     if(stream->status_code / 100 != 1) {
       stream->resp_hds_complete = TRUE;
@@ -1229,7 +1254,6 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
   struct cf_h2_ctx *ctx = cf->ctx;
   struct h2_stream_ctx *stream;
   struct Curl_easy *data_s;
-  CURLcode result;
   (void)flags;
 
   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
@@ -1252,9 +1276,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
   if(!stream)
     return NGHTTP2_ERR_CALLBACK_FAILURE;
 
-  result = Curl_xfer_write_resp(data_s, (char *)mem, len, FALSE);
-  if(result && result != CURLE_AGAIN)
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  h2_xfer_write_resp(cf, data_s, stream, (char *)mem, len, FALSE);
 
   nghttp2_session_consume(ctx->h2, stream_id, len);
   stream->nrcvd_data += (curl_off_t)len;
@@ -1465,16 +1487,12 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
     result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO);
     if(result)
       return NGHTTP2_ERR_CALLBACK_FAILURE;
-    result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 "));
-    if(result)
-      return NGHTTP2_ERR_CALLBACK_FAILURE;
-    result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen);
-    if(result)
-      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    h2_xfer_write_resp_hd(cf, data_s, stream, STRCONST("HTTP/2 "), FALSE);
+    h2_xfer_write_resp_hd(cf, data_s, stream,
+                          (const char *)value, valuelen, FALSE);
     /* the space character after the status code is mandatory */
-    result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n"));
-    if(result)
-      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    h2_xfer_write_resp_hd(cf, data_s, stream, STRCONST(" \r\n"), FALSE);
+
     /* if we receive data for another handle, wake that up */
     if(CF_DATA_CURRENT(cf) != data_s)
       Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
@@ -1487,18 +1505,13 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
   /* nghttp2 guarantees that namelen > 0, and :status was already
      received, and this is not pseudo-header field . */
   /* convert to an HTTP1-style header */
-  result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen);
-  if(result)
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
-  result = recvbuf_write_hds(cf, data_s, STRCONST(": "));
-  if(result)
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
-  result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen);
-  if(result)
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
-  result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n"));
-  if(result)
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  h2_xfer_write_resp_hd(cf, data_s, stream,
+                        (const char *)name, namelen, FALSE);
+  h2_xfer_write_resp_hd(cf, data_s, stream, STRCONST(": "), FALSE);
+  h2_xfer_write_resp_hd(cf, data_s, stream,
+                        (const char *)value, valuelen, FALSE);
+  h2_xfer_write_resp_hd(cf, data_s, stream, STRCONST("\r\n"), FALSE);
+
   /* if we receive data for another handle, wake that up */
   if(CF_DATA_CURRENT(cf) != data_s)
     Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
@@ -1799,7 +1812,12 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
 
   (void)buf;
   *err = CURLE_AGAIN;
-  if(stream->closed) {
+  if(stream->xfer_result) {
+    CURL_TRC_CF(data, cf, "[%d] xfer write failed", stream->id);
+    *err = stream->xfer_result;
+    nread = -1;
+  }
+  else if(stream->closed) {
     CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id);
     nread = http2_handle_stream_close(cf, data, stream, err);
   }
--- a/lib/vquic/curl_ngtcp2.c
+++ b/lib/vquic/curl_ngtcp2.c
@@ -152,6 +152,7 @@ struct h3_stream_ctx {
   uint64_t error3; /* HTTP/3 stream error code */
   curl_off_t upload_left; /* number of request bytes left to upload */
   int status_code; /* HTTP status code */
+  CURLcode xfer_result; /* result from xfer_resp_write(_hd) */
   bool resp_hds_complete; /* we have a complete, final response */
   bool closed; /* TRUE on stream close */
   bool reset;  /* TRUE on stream reset */
@@ -759,10 +760,39 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
   return 0;
 }
 
-static CURLcode write_resp_hds(struct Curl_easy *data,
-                               const char *buf, size_t blen)
+static void h3_xfer_write_resp_hd(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  struct h3_stream_ctx *stream,
+                                  const char *buf, size_t blen, bool eos)
 {
-  return Curl_xfer_write_resp(data, (char *)buf, blen, FALSE);
+
+  /* If we already encountered an error, skip further writes */
+  if(!stream->xfer_result) {
+    stream->xfer_result = Curl_xfer_write_resp(data, (char *)buf, blen, eos);
+    if(stream->xfer_result)
+      CURL_TRC_CF(data, cf, "[%"PRId64"] error %d writing %zu "
+                  "bytes of headers", stream->id, stream->xfer_result, blen);
+  }
+}
+
+static void h3_xfer_write_resp(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               struct h3_stream_ctx *stream,
+                               const char *buf, size_t blen, bool eos)
+{
+
+  /* If we already encountered an error, skip further writes */
+  if(!stream->xfer_result)
+    stream->xfer_result = Curl_xfer_write_resp(data, (char *)buf, blen, eos);
+  /* If the transfer write is errored, we do not want any more data */
+  if(stream->xfer_result) {
+    struct cf_ngtcp2_ctx *ctx = cf->ctx;
+    CURL_TRC_CF(data, cf, "[%"PRId64"] error %d writing %zu bytes "
+                "of data, cancelling stream",
+                stream->id, stream->xfer_result, blen);
+    nghttp3_conn_close_stream(ctx->h3conn, stream->id,
+                              NGHTTP3_H3_REQUEST_CANCELLED);
+  }
 }
 
 static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id,
@@ -773,7 +803,6 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id,
   struct cf_ngtcp2_ctx *ctx = cf->ctx;
   struct Curl_easy *data = stream_user_data;
   struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
-  CURLcode result;
 
   (void)conn;
   (void)stream3_id;
@@ -781,12 +810,7 @@ static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id,
   if(!stream)
     return NGHTTP3_ERR_CALLBACK_FAILURE;
 
-  result = Curl_xfer_write_resp(data, (char *)buf, blen, FALSE);
-  if(result) {
-    CURL_TRC_CF(data, cf, "[%" PRId64 "] DATA len=%zu, ERROR receiving %d",
-                stream->id, blen, result);
-    return NGHTTP3_ERR_CALLBACK_FAILURE;
-  }
+  h3_xfer_write_resp(cf, data, stream, (char *)buf, blen, FALSE);
   if(blen) {
     CURL_TRC_CF(data, cf, "[%" PRId64 "] ACK %zu bytes of DATA",
                 stream->id, blen);
@@ -819,7 +843,6 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
   struct Curl_cfilter *cf = user_data;
   struct Curl_easy *data = stream_user_data;
   struct h3_stream_ctx *stream = H3_STREAM_CTX(data);
-  CURLcode result = CURLE_OK;
   (void)conn;
   (void)stream_id;
   (void)fin;
@@ -828,10 +851,7 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
   if(!stream)
     return 0;
   /* add a CRLF only if we've received some headers */
-  result = write_resp_hds(data, "\r\n", 2);
-  if(result) {
-    return -1;
-  }
+  h3_xfer_write_resp_hd(cf, data, stream, STRCONST("\r\n"), stream->closed);
 
   CURL_TRC_CF(data, cf, "[%" PRId64 "] end_headers, status=%d",
               stream_id, stream->status_code);
@@ -874,7 +894,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
     ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
                       stream->status_code);
     CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s", stream_id, line);
-    result = write_resp_hds(data, line, ncopy);
+    h3_xfer_write_resp_hd(cf, data, stream, line, ncopy, FALSE);
     if(result) {
       return -1;
     }
@@ -884,22 +904,12 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
     CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s",
                 stream_id, (int)h3name.len, h3name.base,
                 (int)h3val.len, h3val.base);
-    result = write_resp_hds(data, (const char *)h3name.base, h3name.len);
-    if(result) {
-      return -1;
-    }
-    result = write_resp_hds(data, ": ", 2);
-    if(result) {
-      return -1;
-    }
-    result = write_resp_hds(data, (const char *)h3val.base, h3val.len);
-    if(result) {
-      return -1;
-    }
-    result = write_resp_hds(data, "\r\n", 2);
-    if(result) {
-      return -1;
-    }
+    h3_xfer_write_resp_hd(cf, data, stream,
+                          (const char *)h3name.base, h3name.len, FALSE);
+    h3_xfer_write_resp_hd(cf, data, stream, ": ", 2, FALSE);
+    h3_xfer_write_resp_hd(cf, data, stream, (
+                          const char *)h3val.base, h3val.len, FALSE);
+    h3_xfer_write_resp_hd(cf, data, stream, "\r\n", 2, FALSE);
   }
   return 0;
 }
@@ -1083,7 +1093,13 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
     goto out;
   }
 
-  if(stream->closed) {
+  if(stream->xfer_result) {
+    CURL_TRC_CF(data, cf, "[%" PRId64 "] xfer write failed", stream->id);
+    *err = stream->xfer_result;
+    nread = -1;
+    goto out;
+  }
+  else if(stream->closed) {
     nread = recv_closed_stream(cf, data, stream, err);
     goto out;
   }
--- a/tests/http/test_02_download.py
+++ b/tests/http/test_02_download.py
@@ -257,6 +257,34 @@ class TestDownload:
         ])
         r.check_response(count=count, http_status=200)
 
+    @pytest.mark.parametrize("proto", ['h2', 'h3'])
+    def test_02_14_not_found(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 stalls here")
+        count = 10
+        urln = f'https://{env.authority_for(env.domain1, proto)}/not-found?[0-{count-1}]'
+        curl = CurlClient(env=env)
+        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
+            '--parallel'
+        ])
+        r.check_stats(count=count, http_status=404, exitcode=0)
+
+    @pytest.mark.parametrize("proto", ['h2', 'h3'])
+    def test_02_15_fail_not_found(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 stalls here")
+        count = 10
+        urln = f'https://{env.authority_for(env.domain1, proto)}/not-found?[0-{count-1}]'
+        curl = CurlClient(env=env)
+        r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
+            '--fail'
+        ])
+        r.check_stats(count=count, http_status=404, exitcode=22)
+
     @pytest.mark.skipif(condition=Env().slow_network, reason="not suitable for slow network tests")
     @pytest.mark.skipif(condition=Env().ci_run, reason="not suitable for CI runs")
     def test_02_20_h2_small_frames(self, env: Env, httpd, repeat):