1 | /*************************************************************************** |
2 | * _ _ ____ _ |
3 | * Project ___| | | | _ \| | |
4 | * / __| | | | |_) | | |
5 | * | (__| |_| | _ <| |___ |
6 | * \___|\___/|_| \_\_____| |
7 | * |
8 | * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | * |
10 | * This software is licensed as described in the file COPYING, which |
11 | * you should have received as part of this distribution. The terms |
12 | * are also available at https://curl.haxx.se/docs/copyright.html. |
13 | * |
14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | * copies of the Software, and permit persons to whom the Software is |
16 | * furnished to do so, under the terms of the COPYING file. |
17 | * |
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | * KIND, either express or implied. |
20 | * |
21 | ***************************************************************************/ |
22 | |
23 | #include "curl_setup.h" |
24 | |
25 | #ifdef USE_NGHTTP2 |
26 | #include <nghttp2/nghttp2.h> |
27 | #include "urldata.h" |
28 | #include "http2.h" |
29 | #include "http.h" |
30 | #include "sendf.h" |
31 | #include "select.h" |
32 | #include "curl_base64.h" |
33 | #include "strcase.h" |
34 | #include "multiif.h" |
35 | #include "url.h" |
36 | #include "connect.h" |
37 | #include "strtoofft.h" |
38 | #include "strdup.h" |
39 | /* The last 3 #include files should be in this order */ |
40 | #include "curl_printf.h" |
41 | #include "curl_memory.h" |
42 | #include "memdebug.h" |
43 | |
44 | #define H2_BUFSIZE 32768 |
45 | |
46 | #if (NGHTTP2_VERSION_NUM < 0x010000) |
47 | #error too old nghttp2 version, upgrade! |
48 | #endif |
49 | |
50 | #if (NGHTTP2_VERSION_NUM > 0x010800) |
51 | #define NGHTTP2_HAS_HTTP2_STRERROR 1 |
52 | #endif |
53 | |
54 | #if (NGHTTP2_VERSION_NUM >= 0x010900) |
55 | /* nghttp2_session_callbacks_set_error_callback is present in nghttp2 1.9.0 or |
56 | later */ |
57 | #define NGHTTP2_HAS_ERROR_CALLBACK 1 |
58 | #else |
59 | #define nghttp2_session_callbacks_set_error_callback(x,y) |
60 | #endif |
61 | |
62 | #if (NGHTTP2_VERSION_NUM >= 0x010c00) |
63 | #define NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 1 |
64 | #endif |
65 | |
66 | #define HTTP2_HUGE_WINDOW_SIZE (1 << 30) |
67 | |
68 | #ifdef DEBUG_HTTP2 |
69 | #define H2BUGF(x) x |
70 | #else |
71 | #define H2BUGF(x) do { } WHILE_FALSE |
72 | #endif |
73 | |
74 | |
75 | static ssize_t http2_recv(struct connectdata *conn, int sockindex, |
76 | char *mem, size_t len, CURLcode *err); |
77 | static bool http2_connisdead(struct connectdata *conn); |
78 | static int h2_session_send(struct Curl_easy *data, |
79 | nghttp2_session *h2); |
80 | static int h2_process_pending_input(struct connectdata *conn, |
81 | struct http_conn *httpc, |
82 | CURLcode *err); |
83 | |
84 | /* |
85 | * Curl_http2_init_state() is called when the easy handle is created and |
86 | * allows for HTTP/2 specific init of state. |
87 | */ |
88 | void Curl_http2_init_state(struct UrlState *state) |
89 | { |
90 | state->stream_weight = NGHTTP2_DEFAULT_WEIGHT; |
91 | } |
92 | |
93 | /* |
94 | * Curl_http2_init_userset() is called when the easy handle is created and |
95 | * allows for HTTP/2 specific user-set fields. |
96 | */ |
97 | void Curl_http2_init_userset(struct UserDefined *set) |
98 | { |
99 | set->stream_weight = NGHTTP2_DEFAULT_WEIGHT; |
100 | } |
101 | |
102 | static int http2_perform_getsock(const struct connectdata *conn, |
103 | curl_socket_t *sock) |
104 | { |
105 | const struct http_conn *c = &conn->proto.httpc; |
106 | struct SingleRequest *k = &conn->data->req; |
107 | int bitmap = GETSOCK_BLANK; |
108 | |
109 | sock[0] = conn->sock[FIRSTSOCKET]; |
110 | |
111 | /* in a HTTP/2 connection we can basically always get a frame so we should |
112 | always be ready for one */ |
113 | bitmap |= GETSOCK_READSOCK(FIRSTSOCKET); |
114 | |
115 | /* we're still uploading or the HTTP/2 layer wants to send data */ |
116 | if(((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) || |
117 | nghttp2_session_want_write(c->h2)) |
118 | bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET); |
119 | |
120 | return bitmap; |
121 | } |
122 | |
123 | static int http2_getsock(struct connectdata *conn, |
124 | curl_socket_t *socks) |
125 | { |
126 | return http2_perform_getsock(conn, socks); |
127 | } |
128 | |
129 | /* |
130 | * http2_stream_free() free HTTP2 stream related data |
131 | */ |
132 | static void http2_stream_free(struct HTTP *http) |
133 | { |
134 | if(http) { |
135 | Curl_add_buffer_free(&http->header_recvbuf); |
136 | Curl_add_buffer_free(&http->trailer_recvbuf); |
137 | for(; http->push_headers_used > 0; --http->push_headers_used) { |
138 | free(http->push_headers[http->push_headers_used - 1]); |
139 | } |
140 | free(http->push_headers); |
141 | http->push_headers = NULL; |
142 | } |
143 | } |
144 | |
145 | /* |
146 | * Disconnects *a* connection used for HTTP/2. It might be an old one from the |
147 | * connection cache and not the "main" one. Don't touch the easy handle! |
148 | */ |
149 | |
150 | static CURLcode http2_disconnect(struct connectdata *conn, |
151 | bool dead_connection) |
152 | { |
153 | struct http_conn *c = &conn->proto.httpc; |
154 | (void)dead_connection; |
155 | |
156 | H2BUGF(infof(conn->data, "HTTP/2 DISCONNECT starts now\n" )); |
157 | |
158 | nghttp2_session_del(c->h2); |
159 | Curl_safefree(c->inbuf); |
160 | |
161 | H2BUGF(infof(conn->data, "HTTP/2 DISCONNECT done\n" )); |
162 | |
163 | return CURLE_OK; |
164 | } |
165 | |
166 | /* |
167 | * The server may send us data at any point (e.g. PING frames). Therefore, |
168 | * we cannot assume that an HTTP/2 socket is dead just because it is readable. |
169 | * |
170 | * Instead, if it is readable, run Curl_connalive() to peek at the socket |
171 | * and distinguish between closed and data. |
172 | */ |
173 | static bool http2_connisdead(struct connectdata *conn) |
174 | { |
175 | int sval; |
176 | bool dead = TRUE; |
177 | |
178 | if(conn->bits.close) |
179 | return TRUE; |
180 | |
181 | sval = SOCKET_READABLE(conn->sock[FIRSTSOCKET], 0); |
182 | if(sval == 0) { |
183 | /* timeout */ |
184 | dead = FALSE; |
185 | } |
186 | else if(sval & CURL_CSELECT_ERR) { |
187 | /* socket is in an error state */ |
188 | dead = TRUE; |
189 | } |
190 | else if(sval & CURL_CSELECT_IN) { |
191 | /* readable with no error. could still be closed */ |
192 | dead = !Curl_connalive(conn); |
193 | if(!dead) { |
194 | /* This happens before we've sent off a request and the connection is |
195 | not in use by any other transfer, there shouldn't be any data here, |
196 | only "protocol frames" */ |
197 | CURLcode result; |
198 | struct http_conn *httpc = &conn->proto.httpc; |
199 | ssize_t nread = -1; |
200 | if(httpc->recv_underlying) |
201 | /* if called "too early", this pointer isn't setup yet! */ |
202 | nread = ((Curl_recv *)httpc->recv_underlying)( |
203 | conn, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result); |
204 | if(nread != -1) { |
205 | infof(conn->data, |
206 | "%d bytes stray data read before trying h2 connection\n" , |
207 | (int)nread); |
208 | httpc->nread_inbuf = 0; |
209 | httpc->inbuflen = nread; |
210 | (void)h2_process_pending_input(conn, httpc, &result); |
211 | } |
212 | else |
213 | /* the read failed so let's say this is dead anyway */ |
214 | dead = TRUE; |
215 | } |
216 | } |
217 | |
218 | return dead; |
219 | } |
220 | |
221 | static unsigned int http2_conncheck(struct connectdata *check, |
222 | unsigned int checks_to_perform) |
223 | { |
224 | unsigned int ret_val = CONNRESULT_NONE; |
225 | struct http_conn *c = &check->proto.httpc; |
226 | int rc; |
227 | bool send_frames = false; |
228 | |
229 | if(checks_to_perform & CONNCHECK_ISDEAD) { |
230 | if(http2_connisdead(check)) |
231 | ret_val |= CONNRESULT_DEAD; |
232 | } |
233 | |
234 | if(checks_to_perform & CONNCHECK_KEEPALIVE) { |
235 | struct curltime now = Curl_now(); |
236 | timediff_t elapsed = Curl_timediff(now, check->keepalive); |
237 | |
238 | if(elapsed > check->upkeep_interval_ms) { |
239 | /* Perform an HTTP/2 PING */ |
240 | rc = nghttp2_submit_ping(c->h2, 0, ZERO_NULL); |
241 | if(!rc) { |
242 | /* Successfully added a PING frame to the session. Need to flag this |
243 | so the frame is sent. */ |
244 | send_frames = true; |
245 | } |
246 | else { |
247 | failf(check->data, "nghttp2_submit_ping() failed: %s(%d)" , |
248 | nghttp2_strerror(rc), rc); |
249 | } |
250 | |
251 | check->keepalive = now; |
252 | } |
253 | } |
254 | |
255 | if(send_frames) { |
256 | rc = nghttp2_session_send(c->h2); |
257 | if(rc) |
258 | failf(check->data, "nghttp2_session_send() failed: %s(%d)" , |
259 | nghttp2_strerror(rc), rc); |
260 | } |
261 | |
262 | return ret_val; |
263 | } |
264 | |
265 | /* called from http_setup_conn */ |
266 | void Curl_http2_setup_req(struct Curl_easy *data) |
267 | { |
268 | struct HTTP *http = data->req.protop; |
269 | |
270 | http->nread_header_recvbuf = 0; |
271 | http->bodystarted = FALSE; |
272 | http->status_code = -1; |
273 | http->pausedata = NULL; |
274 | http->pauselen = 0; |
275 | http->closed = FALSE; |
276 | http->close_handled = FALSE; |
277 | http->mem = data->state.buffer; |
278 | http->len = data->set.buffer_size; |
279 | http->memlen = 0; |
280 | } |
281 | |
282 | /* called from http_setup_conn */ |
283 | void Curl_http2_setup_conn(struct connectdata *conn) |
284 | { |
285 | conn->proto.httpc.settings.max_concurrent_streams = |
286 | DEFAULT_MAX_CONCURRENT_STREAMS; |
287 | conn->proto.httpc.error_code = NGHTTP2_NO_ERROR; |
288 | } |
289 | |
290 | /* |
291 | * HTTP2 handler interface. This isn't added to the general list of protocols |
292 | * but will be used at run-time when the protocol is dynamically switched from |
293 | * HTTP to HTTP2. |
294 | */ |
295 | static const struct Curl_handler Curl_handler_http2 = { |
296 | "HTTP" , /* scheme */ |
297 | ZERO_NULL, /* setup_connection */ |
298 | Curl_http, /* do_it */ |
299 | Curl_http_done, /* done */ |
300 | ZERO_NULL, /* do_more */ |
301 | ZERO_NULL, /* connect_it */ |
302 | ZERO_NULL, /* connecting */ |
303 | ZERO_NULL, /* doing */ |
304 | http2_getsock, /* proto_getsock */ |
305 | http2_getsock, /* doing_getsock */ |
306 | ZERO_NULL, /* domore_getsock */ |
307 | http2_perform_getsock, /* perform_getsock */ |
308 | http2_disconnect, /* disconnect */ |
309 | ZERO_NULL, /* readwrite */ |
310 | http2_conncheck, /* connection_check */ |
311 | PORT_HTTP, /* defport */ |
312 | CURLPROTO_HTTP, /* protocol */ |
313 | PROTOPT_STREAM /* flags */ |
314 | }; |
315 | |
316 | static const struct Curl_handler Curl_handler_http2_ssl = { |
317 | "HTTPS" , /* scheme */ |
318 | ZERO_NULL, /* setup_connection */ |
319 | Curl_http, /* do_it */ |
320 | Curl_http_done, /* done */ |
321 | ZERO_NULL, /* do_more */ |
322 | ZERO_NULL, /* connect_it */ |
323 | ZERO_NULL, /* connecting */ |
324 | ZERO_NULL, /* doing */ |
325 | http2_getsock, /* proto_getsock */ |
326 | http2_getsock, /* doing_getsock */ |
327 | ZERO_NULL, /* domore_getsock */ |
328 | http2_perform_getsock, /* perform_getsock */ |
329 | http2_disconnect, /* disconnect */ |
330 | ZERO_NULL, /* readwrite */ |
331 | http2_conncheck, /* connection_check */ |
332 | PORT_HTTP, /* defport */ |
333 | CURLPROTO_HTTPS, /* protocol */ |
334 | PROTOPT_SSL | PROTOPT_STREAM /* flags */ |
335 | }; |
336 | |
337 | /* |
338 | * Store nghttp2 version info in this buffer, Prefix with a space. Return |
339 | * total length written. |
340 | */ |
341 | int Curl_http2_ver(char *p, size_t len) |
342 | { |
343 | nghttp2_info *h2 = nghttp2_version(0); |
344 | return msnprintf(p, len, " nghttp2/%s" , h2->version_str); |
345 | } |
346 | |
347 | /* HTTP/2 error code to name based on the Error Code Registry. |
348 | https://tools.ietf.org/html/rfc7540#page-77 |
349 | nghttp2_error_code enums are identical. |
350 | */ |
351 | static const char *http2_strerror(uint32_t err) |
352 | { |
353 | #ifndef NGHTTP2_HAS_HTTP2_STRERROR |
354 | const char *str[] = { |
355 | "NO_ERROR" , /* 0x0 */ |
356 | "PROTOCOL_ERROR" , /* 0x1 */ |
357 | "INTERNAL_ERROR" , /* 0x2 */ |
358 | "FLOW_CONTROL_ERROR" , /* 0x3 */ |
359 | "SETTINGS_TIMEOUT" , /* 0x4 */ |
360 | "STREAM_CLOSED" , /* 0x5 */ |
361 | "FRAME_SIZE_ERROR" , /* 0x6 */ |
362 | "REFUSED_STREAM" , /* 0x7 */ |
363 | "CANCEL" , /* 0x8 */ |
364 | "COMPRESSION_ERROR" , /* 0x9 */ |
365 | "CONNECT_ERROR" , /* 0xA */ |
366 | "ENHANCE_YOUR_CALM" , /* 0xB */ |
367 | "INADEQUATE_SECURITY" , /* 0xC */ |
368 | "HTTP_1_1_REQUIRED" /* 0xD */ |
369 | }; |
370 | return (err < sizeof(str) / sizeof(str[0])) ? str[err] : "unknown" ; |
371 | #else |
372 | return nghttp2_http2_strerror(err); |
373 | #endif |
374 | } |
375 | |
376 | /* |
377 | * The implementation of nghttp2_send_callback type. Here we write |data| with |
378 | * size |length| to the network and return the number of bytes actually |
379 | * written. See the documentation of nghttp2_send_callback for the details. |
380 | */ |
381 | static ssize_t send_callback(nghttp2_session *h2, |
382 | const uint8_t *data, size_t length, int flags, |
383 | void *userp) |
384 | { |
385 | struct connectdata *conn = (struct connectdata *)userp; |
386 | struct http_conn *c = &conn->proto.httpc; |
387 | ssize_t written; |
388 | CURLcode result = CURLE_OK; |
389 | |
390 | (void)h2; |
391 | (void)flags; |
392 | |
393 | if(!c->send_underlying) |
394 | /* called before setup properly! */ |
395 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
396 | |
397 | written = ((Curl_send*)c->send_underlying)(conn, FIRSTSOCKET, |
398 | data, length, &result); |
399 | |
400 | if(result == CURLE_AGAIN) { |
401 | return NGHTTP2_ERR_WOULDBLOCK; |
402 | } |
403 | |
404 | if(written == -1) { |
405 | failf(conn->data, "Failed sending HTTP2 data" ); |
406 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
407 | } |
408 | |
409 | if(!written) |
410 | return NGHTTP2_ERR_WOULDBLOCK; |
411 | |
412 | return written; |
413 | } |
414 | |
415 | |
416 | /* We pass a pointer to this struct in the push callback, but the contents of |
417 | the struct are hidden from the user. */ |
418 | struct curl_pushheaders { |
419 | struct Curl_easy *data; |
420 | const nghttp2_push_promise *frame; |
421 | }; |
422 | |
423 | /* |
424 | * push header access function. Only to be used from within the push callback |
425 | */ |
426 | char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) |
427 | { |
428 | /* Verify that we got a good easy handle in the push header struct, mostly to |
429 | detect rubbish input fast(er). */ |
430 | if(!h || !GOOD_EASY_HANDLE(h->data)) |
431 | return NULL; |
432 | else { |
433 | struct HTTP *stream = h->data->req.protop; |
434 | if(num < stream->push_headers_used) |
435 | return stream->push_headers[num]; |
436 | } |
437 | return NULL; |
438 | } |
439 | |
440 | /* |
441 | * push header access function. Only to be used from within the push callback |
442 | */ |
443 | char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) |
444 | { |
445 | /* Verify that we got a good easy handle in the push header struct, |
446 | mostly to detect rubbish input fast(er). Also empty header name |
447 | is just a rubbish too. We have to allow ":" at the beginning of |
448 | the header, but header == ":" must be rejected. If we have ':' in |
449 | the middle of header, it could be matched in middle of the value, |
450 | this is because we do prefix match.*/ |
451 | if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] || |
452 | !strcmp(header, ":" ) || strchr(header + 1, ':')) |
453 | return NULL; |
454 | else { |
455 | struct HTTP *stream = h->data->req.protop; |
456 | size_t len = strlen(header); |
457 | size_t i; |
458 | for(i = 0; i<stream->push_headers_used; i++) { |
459 | if(!strncmp(header, stream->push_headers[i], len)) { |
460 | /* sub-match, make sure that it is followed by a colon */ |
461 | if(stream->push_headers[i][len] != ':') |
462 | continue; |
463 | return &stream->push_headers[i][len + 1]; |
464 | } |
465 | } |
466 | } |
467 | return NULL; |
468 | } |
469 | |
470 | /* |
471 | * This specific transfer on this connection has been "drained". |
472 | */ |
473 | static void drained_transfer(struct Curl_easy *data, |
474 | struct http_conn *httpc) |
475 | { |
476 | DEBUGASSERT(httpc->drain_total >= data->state.drain); |
477 | httpc->drain_total -= data->state.drain; |
478 | data->state.drain = 0; |
479 | } |
480 | |
481 | /* |
482 | * Mark this transfer to get "drained". |
483 | */ |
484 | static void drain_this(struct Curl_easy *data, |
485 | struct http_conn *httpc) |
486 | { |
487 | data->state.drain++; |
488 | httpc->drain_total++; |
489 | DEBUGASSERT(httpc->drain_total >= data->state.drain); |
490 | } |
491 | |
492 | static struct Curl_easy *duphandle(struct Curl_easy *data) |
493 | { |
494 | struct Curl_easy *second = curl_easy_duphandle(data); |
495 | if(second) { |
496 | /* setup the request struct */ |
497 | struct HTTP *http = calloc(1, sizeof(struct HTTP)); |
498 | if(!http) { |
499 | (void)Curl_close(&second); |
500 | } |
501 | else { |
502 | second->req.protop = http; |
503 | http->header_recvbuf = Curl_add_buffer_init(); |
504 | if(!http->header_recvbuf) { |
505 | free(http); |
506 | (void)Curl_close(&second); |
507 | } |
508 | else { |
509 | Curl_http2_setup_req(second); |
510 | second->state.stream_weight = data->state.stream_weight; |
511 | } |
512 | } |
513 | } |
514 | return second; |
515 | } |
516 | |
517 | |
518 | static int push_promise(struct Curl_easy *data, |
519 | struct connectdata *conn, |
520 | const nghttp2_push_promise *frame) |
521 | { |
522 | int rv; |
523 | H2BUGF(infof(data, "PUSH_PROMISE received, stream %u!\n" , |
524 | frame->promised_stream_id)); |
525 | if(data->multi->push_cb) { |
526 | struct HTTP *stream; |
527 | struct HTTP *newstream; |
528 | struct curl_pushheaders heads; |
529 | CURLMcode rc; |
530 | struct http_conn *httpc; |
531 | size_t i; |
532 | /* clone the parent */ |
533 | struct Curl_easy *newhandle = duphandle(data); |
534 | if(!newhandle) { |
535 | infof(data, "failed to duplicate handle\n" ); |
536 | rv = 1; /* FAIL HARD */ |
537 | goto fail; |
538 | } |
539 | |
540 | heads.data = data; |
541 | heads.frame = frame; |
542 | /* ask the application */ |
543 | H2BUGF(infof(data, "Got PUSH_PROMISE, ask application!\n" )); |
544 | |
545 | stream = data->req.protop; |
546 | if(!stream) { |
547 | failf(data, "Internal NULL stream!\n" ); |
548 | (void)Curl_close(&newhandle); |
549 | rv = 1; |
550 | goto fail; |
551 | } |
552 | |
553 | Curl_set_in_callback(data, true); |
554 | rv = data->multi->push_cb(data, newhandle, |
555 | stream->push_headers_used, &heads, |
556 | data->multi->push_userp); |
557 | Curl_set_in_callback(data, false); |
558 | |
559 | /* free the headers again */ |
560 | for(i = 0; i<stream->push_headers_used; i++) |
561 | free(stream->push_headers[i]); |
562 | free(stream->push_headers); |
563 | stream->push_headers = NULL; |
564 | stream->push_headers_used = 0; |
565 | |
566 | if(rv) { |
567 | /* denied, kill off the new handle again */ |
568 | http2_stream_free(newhandle->req.protop); |
569 | newhandle->req.protop = NULL; |
570 | (void)Curl_close(&newhandle); |
571 | goto fail; |
572 | } |
573 | |
574 | newstream = newhandle->req.protop; |
575 | newstream->stream_id = frame->promised_stream_id; |
576 | newhandle->req.maxdownload = -1; |
577 | newhandle->req.size = -1; |
578 | |
579 | /* approved, add to the multi handle and immediately switch to PERFORM |
580 | state with the given connection !*/ |
581 | rc = Curl_multi_add_perform(data->multi, newhandle, conn); |
582 | if(rc) { |
583 | infof(data, "failed to add handle to multi\n" ); |
584 | http2_stream_free(newhandle->req.protop); |
585 | newhandle->req.protop = NULL; |
586 | Curl_close(&newhandle); |
587 | rv = 1; |
588 | goto fail; |
589 | } |
590 | |
591 | httpc = &conn->proto.httpc; |
592 | rv = nghttp2_session_set_stream_user_data(httpc->h2, |
593 | frame->promised_stream_id, |
594 | newhandle); |
595 | if(rv) { |
596 | infof(data, "failed to set user_data for stream %d\n" , |
597 | frame->promised_stream_id); |
598 | DEBUGASSERT(0); |
599 | goto fail; |
600 | } |
601 | } |
602 | else { |
603 | H2BUGF(infof(data, "Got PUSH_PROMISE, ignore it!\n" )); |
604 | rv = 1; |
605 | } |
606 | fail: |
607 | return rv; |
608 | } |
609 | |
610 | /* |
611 | * multi_connchanged() is called to tell that there is a connection in |
612 | * this multi handle that has changed state (multiplexing become possible, the |
613 | * number of allowed streams changed or similar), and a subsequent use of this |
614 | * multi handle should move CONNECT_PEND handles back to CONNECT to have them |
615 | * retry. |
616 | */ |
617 | static void multi_connchanged(struct Curl_multi *multi) |
618 | { |
619 | multi->recheckstate = TRUE; |
620 | } |
621 | |
622 | static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, |
623 | void *userp) |
624 | { |
625 | struct connectdata *conn = (struct connectdata *)userp; |
626 | struct http_conn *httpc = &conn->proto.httpc; |
627 | struct Curl_easy *data_s = NULL; |
628 | struct HTTP *stream = NULL; |
629 | int rv; |
630 | size_t left, ncopy; |
631 | int32_t stream_id = frame->hd.stream_id; |
632 | CURLcode result; |
633 | |
634 | if(!stream_id) { |
635 | /* stream ID zero is for connection-oriented stuff */ |
636 | if(frame->hd.type == NGHTTP2_SETTINGS) { |
637 | uint32_t max_conn = httpc->settings.max_concurrent_streams; |
638 | H2BUGF(infof(conn->data, "Got SETTINGS\n" )); |
639 | httpc->settings.max_concurrent_streams = |
640 | nghttp2_session_get_remote_settings( |
641 | session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); |
642 | httpc->settings.enable_push = |
643 | nghttp2_session_get_remote_settings( |
644 | session, NGHTTP2_SETTINGS_ENABLE_PUSH); |
645 | H2BUGF(infof(conn->data, "MAX_CONCURRENT_STREAMS == %d\n" , |
646 | httpc->settings.max_concurrent_streams)); |
647 | H2BUGF(infof(conn->data, "ENABLE_PUSH == %s\n" , |
648 | httpc->settings.enable_push?"TRUE" :"false" )); |
649 | if(max_conn != httpc->settings.max_concurrent_streams) { |
650 | /* only signal change if the value actually changed */ |
651 | infof(conn->data, |
652 | "Connection state changed (MAX_CONCURRENT_STREAMS == %u)!\n" , |
653 | httpc->settings.max_concurrent_streams); |
654 | multi_connchanged(conn->data->multi); |
655 | } |
656 | } |
657 | return 0; |
658 | } |
659 | data_s = nghttp2_session_get_stream_user_data(session, stream_id); |
660 | if(!data_s) { |
661 | H2BUGF(infof(conn->data, |
662 | "No Curl_easy associated with stream: %x\n" , |
663 | stream_id)); |
664 | return 0; |
665 | } |
666 | |
667 | stream = data_s->req.protop; |
668 | if(!stream) { |
669 | H2BUGF(infof(data_s, "No proto pointer for stream: %x\n" , |
670 | stream_id)); |
671 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
672 | } |
673 | |
674 | H2BUGF(infof(data_s, "on_frame_recv() header %x stream %x\n" , |
675 | frame->hd.type, stream_id)); |
676 | |
677 | switch(frame->hd.type) { |
678 | case NGHTTP2_DATA: |
679 | /* If body started on this stream, then receiving DATA is illegal. */ |
680 | if(!stream->bodystarted) { |
681 | rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, |
682 | stream_id, NGHTTP2_PROTOCOL_ERROR); |
683 | |
684 | if(nghttp2_is_fatal(rv)) { |
685 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
686 | } |
687 | } |
688 | break; |
689 | case NGHTTP2_HEADERS: |
690 | if(stream->bodystarted) { |
691 | /* Only valid HEADERS after body started is trailer HEADERS. We |
692 | buffer them in on_header callback. */ |
693 | break; |
694 | } |
695 | |
696 | /* nghttp2 guarantees that :status is received, and we store it to |
697 | stream->status_code. Fuzzing has proven this can still be reached |
698 | without status code having been set. */ |
699 | if(stream->status_code == -1) |
700 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
701 | |
702 | /* Only final status code signals the end of header */ |
703 | if(stream->status_code / 100 != 1) { |
704 | stream->bodystarted = TRUE; |
705 | stream->status_code = -1; |
706 | } |
707 | |
708 | result = Curl_add_buffer(&stream->header_recvbuf, "\r\n" , 2); |
709 | if(result) |
710 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
711 | |
712 | left = stream->header_recvbuf->size_used - stream->nread_header_recvbuf; |
713 | ncopy = CURLMIN(stream->len, left); |
714 | |
715 | memcpy(&stream->mem[stream->memlen], |
716 | stream->header_recvbuf->buffer + stream->nread_header_recvbuf, |
717 | ncopy); |
718 | stream->nread_header_recvbuf += ncopy; |
719 | |
720 | H2BUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p\n" , |
721 | ncopy, stream_id, stream->mem)); |
722 | |
723 | stream->len -= ncopy; |
724 | stream->memlen += ncopy; |
725 | |
726 | drain_this(data_s, httpc); |
727 | { |
728 | /* get the pointer from userp again since it was re-assigned above */ |
729 | struct connectdata *conn_s = (struct connectdata *)userp; |
730 | |
731 | /* if we receive data for another handle, wake that up */ |
732 | if(conn_s->data != data_s) |
733 | Curl_expire(data_s, 0, EXPIRE_RUN_NOW); |
734 | } |
735 | break; |
736 | case NGHTTP2_PUSH_PROMISE: |
737 | rv = push_promise(data_s, conn, &frame->push_promise); |
738 | if(rv) { /* deny! */ |
739 | rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, |
740 | frame->push_promise.promised_stream_id, |
741 | NGHTTP2_CANCEL); |
742 | if(nghttp2_is_fatal(rv)) { |
743 | return rv; |
744 | } |
745 | } |
746 | break; |
747 | default: |
748 | H2BUGF(infof(data_s, "Got frame type %x for stream %u!\n" , |
749 | frame->hd.type, stream_id)); |
750 | break; |
751 | } |
752 | return 0; |
753 | } |
754 | |
755 | static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, |
756 | int32_t stream_id, |
757 | const uint8_t *data, size_t len, void *userp) |
758 | { |
759 | struct HTTP *stream; |
760 | struct Curl_easy *data_s; |
761 | size_t nread; |
762 | struct connectdata *conn = (struct connectdata *)userp; |
763 | (void)session; |
764 | (void)flags; |
765 | (void)data; |
766 | |
767 | DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ |
768 | |
769 | /* get the stream from the hash based on Stream ID */ |
770 | data_s = nghttp2_session_get_stream_user_data(session, stream_id); |
771 | if(!data_s) |
772 | /* Receiving a Stream ID not in the hash should not happen, this is an |
773 | internal error more than anything else! */ |
774 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
775 | |
776 | stream = data_s->req.protop; |
777 | if(!stream) |
778 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
779 | |
780 | nread = CURLMIN(stream->len, len); |
781 | memcpy(&stream->mem[stream->memlen], data, nread); |
782 | |
783 | stream->len -= nread; |
784 | stream->memlen += nread; |
785 | |
786 | drain_this(data_s, &conn->proto.httpc); |
787 | |
788 | /* if we receive data for another handle, wake that up */ |
789 | if(conn->data != data_s) |
790 | Curl_expire(data_s, 0, EXPIRE_RUN_NOW); |
791 | |
792 | H2BUGF(infof(data_s, "%zu data received for stream %u " |
793 | "(%zu left in buffer %p, total %zu)\n" , |
794 | nread, stream_id, |
795 | stream->len, stream->mem, |
796 | stream->memlen)); |
797 | |
798 | if(nread < len) { |
799 | stream->pausedata = data + nread; |
800 | stream->pauselen = len - nread; |
801 | H2BUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer" |
802 | ", stream %u\n" , |
803 | len - nread, stream_id)); |
804 | data_s->conn->proto.httpc.pause_stream_id = stream_id; |
805 | |
806 | return NGHTTP2_ERR_PAUSE; |
807 | } |
808 | |
809 | /* pause execution of nghttp2 if we received data for another handle |
810 | in order to process them first. */ |
811 | if(conn->data != data_s) { |
812 | data_s->conn->proto.httpc.pause_stream_id = stream_id; |
813 | |
814 | return NGHTTP2_ERR_PAUSE; |
815 | } |
816 | |
817 | return 0; |
818 | } |
819 | |
820 | static int on_stream_close(nghttp2_session *session, int32_t stream_id, |
821 | uint32_t error_code, void *userp) |
822 | { |
823 | struct Curl_easy *data_s; |
824 | struct HTTP *stream; |
825 | struct connectdata *conn = (struct connectdata *)userp; |
826 | int rv; |
827 | (void)session; |
828 | (void)stream_id; |
829 | |
830 | if(stream_id) { |
831 | struct http_conn *httpc; |
832 | /* get the stream from the hash based on Stream ID, stream ID zero is for |
833 | connection-oriented stuff */ |
834 | data_s = nghttp2_session_get_stream_user_data(session, stream_id); |
835 | if(!data_s) { |
836 | /* We could get stream ID not in the hash. For example, if we |
837 | decided to reject stream (e.g., PUSH_PROMISE). */ |
838 | return 0; |
839 | } |
840 | H2BUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u\n" , |
841 | http2_strerror(error_code), error_code, stream_id)); |
842 | stream = data_s->req.protop; |
843 | if(!stream) |
844 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
845 | |
846 | stream->closed = TRUE; |
847 | httpc = &conn->proto.httpc; |
848 | drain_this(data_s, httpc); |
849 | Curl_expire(data_s, 0, EXPIRE_RUN_NOW); |
850 | httpc->error_code = error_code; |
851 | |
852 | /* remove the entry from the hash as the stream is now gone */ |
853 | rv = nghttp2_session_set_stream_user_data(session, stream_id, 0); |
854 | if(rv) { |
855 | infof(data_s, "http/2: failed to clear user_data for stream %d!\n" , |
856 | stream_id); |
857 | DEBUGASSERT(0); |
858 | } |
859 | if(stream_id == httpc->pause_stream_id) { |
860 | H2BUGF(infof(data_s, "Stopped the pause stream!\n" )); |
861 | httpc->pause_stream_id = 0; |
862 | } |
863 | H2BUGF(infof(data_s, "Removed stream %u hash!\n" , stream_id)); |
864 | stream->stream_id = 0; /* cleared */ |
865 | } |
866 | return 0; |
867 | } |
868 | |
869 | static int on_begin_headers(nghttp2_session *session, |
870 | const nghttp2_frame *frame, void *userp) |
871 | { |
872 | struct HTTP *stream; |
873 | struct Curl_easy *data_s = NULL; |
874 | (void)userp; |
875 | |
876 | data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); |
877 | if(!data_s) { |
878 | return 0; |
879 | } |
880 | |
881 | H2BUGF(infof(data_s, "on_begin_headers() was called\n" )); |
882 | |
883 | if(frame->hd.type != NGHTTP2_HEADERS) { |
884 | return 0; |
885 | } |
886 | |
887 | stream = data_s->req.protop; |
888 | if(!stream || !stream->bodystarted) { |
889 | return 0; |
890 | } |
891 | |
892 | if(!stream->trailer_recvbuf) { |
893 | stream->trailer_recvbuf = Curl_add_buffer_init(); |
894 | if(!stream->trailer_recvbuf) { |
895 | return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; |
896 | } |
897 | } |
898 | return 0; |
899 | } |
900 | |
901 | /* Decode HTTP status code. Returns -1 if no valid status code was |
902 | decoded. */ |
903 | static int decode_status_code(const uint8_t *value, size_t len) |
904 | { |
905 | int i; |
906 | int res; |
907 | |
908 | if(len != 3) { |
909 | return -1; |
910 | } |
911 | |
912 | res = 0; |
913 | |
914 | for(i = 0; i < 3; ++i) { |
915 | char c = value[i]; |
916 | |
917 | if(c < '0' || c > '9') { |
918 | return -1; |
919 | } |
920 | |
921 | res *= 10; |
922 | res += c - '0'; |
923 | } |
924 | |
925 | return res; |
926 | } |
927 | |
928 | /* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ |
929 | static int on_header(nghttp2_session *session, const nghttp2_frame *frame, |
930 | const uint8_t *name, size_t namelen, |
931 | const uint8_t *value, size_t valuelen, |
932 | uint8_t flags, |
933 | void *userp) |
934 | { |
935 | struct HTTP *stream; |
936 | struct Curl_easy *data_s; |
937 | int32_t stream_id = frame->hd.stream_id; |
938 | struct connectdata *conn = (struct connectdata *)userp; |
939 | CURLcode result; |
940 | (void)flags; |
941 | |
942 | DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ |
943 | |
944 | /* get the stream from the hash based on Stream ID */ |
945 | data_s = nghttp2_session_get_stream_user_data(session, stream_id); |
946 | if(!data_s) |
947 | /* Receiving a Stream ID not in the hash should not happen, this is an |
948 | internal error more than anything else! */ |
949 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
950 | |
951 | stream = data_s->req.protop; |
952 | if(!stream) { |
953 | failf(data_s, "Internal NULL stream! 5\n" ); |
954 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
955 | } |
956 | |
957 | /* Store received PUSH_PROMISE headers to be used when the subsequent |
958 | PUSH_PROMISE callback comes */ |
959 | if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { |
960 | char *h; |
961 | |
962 | if(!strcmp(":authority" , (const char *)name)) { |
963 | /* pseudo headers are lower case */ |
964 | int rc = 0; |
965 | char *check = aprintf("%s:%d" , conn->host.name, conn->remote_port); |
966 | if(!check) |
967 | /* no memory */ |
968 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
969 | if(!Curl_strcasecompare(check, (const char *)value) && |
970 | ((conn->remote_port != conn->given->defport) || |
971 | !Curl_strcasecompare(conn->host.name, (const char *)value))) { |
972 | /* This is push is not for the same authority that was asked for in |
973 | * the URL. RFC 7540 section 8.2 says: "A client MUST treat a |
974 | * PUSH_PROMISE for which the server is not authoritative as a stream |
975 | * error of type PROTOCOL_ERROR." |
976 | */ |
977 | (void)nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, |
978 | stream_id, NGHTTP2_PROTOCOL_ERROR); |
979 | rc = NGHTTP2_ERR_CALLBACK_FAILURE; |
980 | } |
981 | free(check); |
982 | if(rc) |
983 | return rc; |
984 | } |
985 | |
986 | if(!stream->push_headers) { |
987 | stream->push_headers_alloc = 10; |
988 | stream->push_headers = malloc(stream->push_headers_alloc * |
989 | sizeof(char *)); |
990 | if(!stream->push_headers) |
991 | return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; |
992 | stream->push_headers_used = 0; |
993 | } |
994 | else if(stream->push_headers_used == |
995 | stream->push_headers_alloc) { |
996 | char **headp; |
997 | stream->push_headers_alloc *= 2; |
998 | headp = Curl_saferealloc(stream->push_headers, |
999 | stream->push_headers_alloc * sizeof(char *)); |
1000 | if(!headp) { |
1001 | stream->push_headers = NULL; |
1002 | return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; |
1003 | } |
1004 | stream->push_headers = headp; |
1005 | } |
1006 | h = aprintf("%s:%s" , name, value); |
1007 | if(h) |
1008 | stream->push_headers[stream->push_headers_used++] = h; |
1009 | return 0; |
1010 | } |
1011 | |
1012 | if(stream->bodystarted) { |
1013 | /* This is trailer fields. */ |
1014 | /* 4 is for ": " and "\r\n". */ |
1015 | uint32_t n = (uint32_t)(namelen + valuelen + 4); |
1016 | |
1017 | H2BUGF(infof(data_s, "h2 trailer: %.*s: %.*s\n" , namelen, name, valuelen, |
1018 | value)); |
1019 | |
1020 | result = Curl_add_buffer(&stream->trailer_recvbuf, &n, sizeof(n)); |
1021 | if(result) |
1022 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1023 | result = Curl_add_buffer(&stream->trailer_recvbuf, name, namelen); |
1024 | if(result) |
1025 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1026 | result = Curl_add_buffer(&stream->trailer_recvbuf, ": " , 2); |
1027 | if(result) |
1028 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1029 | result = Curl_add_buffer(&stream->trailer_recvbuf, value, valuelen); |
1030 | if(result) |
1031 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1032 | result = Curl_add_buffer(&stream->trailer_recvbuf, "\r\n\0" , 3); |
1033 | if(result) |
1034 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1035 | |
1036 | return 0; |
1037 | } |
1038 | |
1039 | if(namelen == sizeof(":status" ) - 1 && |
1040 | memcmp(":status" , name, namelen) == 0) { |
1041 | /* nghttp2 guarantees :status is received first and only once, and |
1042 | value is 3 digits status code, and decode_status_code always |
1043 | succeeds. */ |
1044 | stream->status_code = decode_status_code(value, valuelen); |
1045 | DEBUGASSERT(stream->status_code != -1); |
1046 | |
1047 | result = Curl_add_buffer(&stream->header_recvbuf, "HTTP/2 " , 7); |
1048 | if(result) |
1049 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1050 | result = Curl_add_buffer(&stream->header_recvbuf, value, valuelen); |
1051 | if(result) |
1052 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1053 | /* the space character after the status code is mandatory */ |
1054 | result = Curl_add_buffer(&stream->header_recvbuf, " \r\n" , 3); |
1055 | if(result) |
1056 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1057 | /* if we receive data for another handle, wake that up */ |
1058 | if(conn->data != data_s) |
1059 | Curl_expire(data_s, 0, EXPIRE_RUN_NOW); |
1060 | |
1061 | H2BUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)\n" , |
1062 | stream->status_code, data_s)); |
1063 | return 0; |
1064 | } |
1065 | |
1066 | /* nghttp2 guarantees that namelen > 0, and :status was already |
1067 | received, and this is not pseudo-header field . */ |
1068 | /* convert to a HTTP1-style header */ |
1069 | result = Curl_add_buffer(&stream->header_recvbuf, name, namelen); |
1070 | if(result) |
1071 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1072 | result = Curl_add_buffer(&stream->header_recvbuf, ": " , 2); |
1073 | if(result) |
1074 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1075 | result = Curl_add_buffer(&stream->header_recvbuf, value, valuelen); |
1076 | if(result) |
1077 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1078 | result = Curl_add_buffer(&stream->header_recvbuf, "\r\n" , 2); |
1079 | if(result) |
1080 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1081 | /* if we receive data for another handle, wake that up */ |
1082 | if(conn->data != data_s) |
1083 | Curl_expire(data_s, 0, EXPIRE_RUN_NOW); |
1084 | |
1085 | H2BUGF(infof(data_s, "h2 header: %.*s: %.*s\n" , namelen, name, valuelen, |
1086 | value)); |
1087 | |
1088 | return 0; /* 0 is successful */ |
1089 | } |
1090 | |
1091 | static ssize_t data_source_read_callback(nghttp2_session *session, |
1092 | int32_t stream_id, |
1093 | uint8_t *buf, size_t length, |
1094 | uint32_t *data_flags, |
1095 | nghttp2_data_source *source, |
1096 | void *userp) |
1097 | { |
1098 | struct Curl_easy *data_s; |
1099 | struct HTTP *stream = NULL; |
1100 | size_t nread; |
1101 | (void)source; |
1102 | (void)userp; |
1103 | |
1104 | if(stream_id) { |
1105 | /* get the stream from the hash based on Stream ID, stream ID zero is for |
1106 | connection-oriented stuff */ |
1107 | data_s = nghttp2_session_get_stream_user_data(session, stream_id); |
1108 | if(!data_s) |
1109 | /* Receiving a Stream ID not in the hash should not happen, this is an |
1110 | internal error more than anything else! */ |
1111 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1112 | |
1113 | stream = data_s->req.protop; |
1114 | if(!stream) |
1115 | return NGHTTP2_ERR_CALLBACK_FAILURE; |
1116 | } |
1117 | else |
1118 | return NGHTTP2_ERR_INVALID_ARGUMENT; |
1119 | |
1120 | nread = CURLMIN(stream->upload_len, length); |
1121 | if(nread > 0) { |
1122 | memcpy(buf, stream->upload_mem, nread); |
1123 | stream->upload_mem += nread; |
1124 | stream->upload_len -= nread; |
1125 | if(data_s->state.infilesize != -1) |
1126 | stream->upload_left -= nread; |
1127 | } |
1128 | |
1129 | if(stream->upload_left == 0) |
1130 | *data_flags = NGHTTP2_DATA_FLAG_EOF; |
1131 | else if(nread == 0) |
1132 | return NGHTTP2_ERR_DEFERRED; |
1133 | |
1134 | H2BUGF(infof(data_s, "data_source_read_callback: " |
1135 | "returns %zu bytes stream %u\n" , |
1136 | nread, stream_id)); |
1137 | |
1138 | return nread; |
1139 | } |
1140 | |
1141 | #if defined(NGHTTP2_HAS_ERROR_CALLBACK) && \ |
1142 | !defined(CURL_DISABLE_VERBOSE_STRINGS) |
1143 | static int error_callback(nghttp2_session *session, |
1144 | const char *msg, |
1145 | size_t len, |
1146 | void *userp) |
1147 | { |
1148 | struct connectdata *conn = (struct connectdata *)userp; |
1149 | (void)session; |
1150 | infof(conn->data, "http2 error: %.*s\n" , len, msg); |
1151 | return 0; |
1152 | } |
1153 | #endif |
1154 | |
1155 | static void populate_settings(struct connectdata *conn, |
1156 | struct http_conn *httpc) |
1157 | { |
1158 | nghttp2_settings_entry *iv = httpc->local_settings; |
1159 | |
1160 | iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; |
1161 | iv[0].value = (uint32_t)Curl_multi_max_concurrent_streams(conn->data->multi); |
1162 | |
1163 | iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; |
1164 | iv[1].value = HTTP2_HUGE_WINDOW_SIZE; |
1165 | |
1166 | iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; |
1167 | iv[2].value = conn->data->multi->push_cb != NULL; |
1168 | |
1169 | httpc->local_settings_num = 3; |
1170 | } |
1171 | |
1172 | void Curl_http2_done(struct connectdata *conn, bool premature) |
1173 | { |
1174 | struct Curl_easy *data = conn->data; |
1175 | struct HTTP *http = data->req.protop; |
1176 | struct http_conn *httpc = &conn->proto.httpc; |
1177 | |
1178 | /* there might be allocated resources done before this got the 'h2' pointer |
1179 | setup */ |
1180 | if(http->header_recvbuf) { |
1181 | Curl_add_buffer_free(&http->header_recvbuf); |
1182 | Curl_add_buffer_free(&http->trailer_recvbuf); |
1183 | if(http->push_headers) { |
1184 | /* if they weren't used and then freed before */ |
1185 | for(; http->push_headers_used > 0; --http->push_headers_used) { |
1186 | free(http->push_headers[http->push_headers_used - 1]); |
1187 | } |
1188 | free(http->push_headers); |
1189 | http->push_headers = NULL; |
1190 | } |
1191 | } |
1192 | |
1193 | if(!httpc->h2) /* not HTTP/2 ? */ |
1194 | return; |
1195 | |
1196 | if(premature) { |
1197 | /* RST_STREAM */ |
1198 | if(!nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE, |
1199 | http->stream_id, NGHTTP2_STREAM_CLOSED)) |
1200 | (void)nghttp2_session_send(httpc->h2); |
1201 | |
1202 | if(http->stream_id == httpc->pause_stream_id) { |
1203 | infof(data, "stopped the pause stream!\n" ); |
1204 | httpc->pause_stream_id = 0; |
1205 | } |
1206 | } |
1207 | |
1208 | if(data->state.drain) |
1209 | drained_transfer(data, httpc); |
1210 | |
1211 | /* -1 means unassigned and 0 means cleared */ |
1212 | if(http->stream_id > 0) { |
1213 | int rv = nghttp2_session_set_stream_user_data(httpc->h2, |
1214 | http->stream_id, 0); |
1215 | if(rv) { |
1216 | infof(data, "http/2: failed to clear user_data for stream %d!\n" , |
1217 | http->stream_id); |
1218 | DEBUGASSERT(0); |
1219 | } |
1220 | http->stream_id = 0; |
1221 | } |
1222 | } |
1223 | |
1224 | /* |
1225 | * Initialize nghttp2 for a Curl connection |
1226 | */ |
1227 | static CURLcode http2_init(struct connectdata *conn) |
1228 | { |
1229 | if(!conn->proto.httpc.h2) { |
1230 | int rc; |
1231 | nghttp2_session_callbacks *callbacks; |
1232 | |
1233 | conn->proto.httpc.inbuf = malloc(H2_BUFSIZE); |
1234 | if(conn->proto.httpc.inbuf == NULL) |
1235 | return CURLE_OUT_OF_MEMORY; |
1236 | |
1237 | rc = nghttp2_session_callbacks_new(&callbacks); |
1238 | |
1239 | if(rc) { |
1240 | failf(conn->data, "Couldn't initialize nghttp2 callbacks!" ); |
1241 | return CURLE_OUT_OF_MEMORY; /* most likely at least */ |
1242 | } |
1243 | |
1244 | /* nghttp2_send_callback */ |
1245 | nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); |
1246 | /* nghttp2_on_frame_recv_callback */ |
1247 | nghttp2_session_callbacks_set_on_frame_recv_callback |
1248 | (callbacks, on_frame_recv); |
1249 | /* nghttp2_on_data_chunk_recv_callback */ |
1250 | nghttp2_session_callbacks_set_on_data_chunk_recv_callback |
1251 | (callbacks, on_data_chunk_recv); |
1252 | /* nghttp2_on_stream_close_callback */ |
1253 | nghttp2_session_callbacks_set_on_stream_close_callback |
1254 | (callbacks, on_stream_close); |
1255 | /* nghttp2_on_begin_headers_callback */ |
1256 | nghttp2_session_callbacks_set_on_begin_headers_callback |
1257 | (callbacks, on_begin_headers); |
1258 | /* nghttp2_on_header_callback */ |
1259 | nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header); |
1260 | |
1261 | #ifndef CURL_DISABLE_VERBOSE_STRINGS |
1262 | nghttp2_session_callbacks_set_error_callback(callbacks, error_callback); |
1263 | #endif |
1264 | |
1265 | /* The nghttp2 session is not yet setup, do it */ |
1266 | rc = nghttp2_session_client_new(&conn->proto.httpc.h2, callbacks, conn); |
1267 | |
1268 | nghttp2_session_callbacks_del(callbacks); |
1269 | |
1270 | if(rc) { |
1271 | failf(conn->data, "Couldn't initialize nghttp2!" ); |
1272 | return CURLE_OUT_OF_MEMORY; /* most likely at least */ |
1273 | } |
1274 | } |
1275 | return CURLE_OK; |
1276 | } |
1277 | |
1278 | /* |
1279 | * Append headers to ask for a HTTP1.1 to HTTP2 upgrade. |
1280 | */ |
1281 | CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req, |
1282 | struct connectdata *conn) |
1283 | { |
1284 | CURLcode result; |
1285 | ssize_t binlen; |
1286 | char *base64; |
1287 | size_t blen; |
1288 | struct SingleRequest *k = &conn->data->req; |
1289 | uint8_t *binsettings = conn->proto.httpc.binsettings; |
1290 | struct http_conn *httpc = &conn->proto.httpc; |
1291 | |
1292 | populate_settings(conn, httpc); |
1293 | |
1294 | /* this returns number of bytes it wrote */ |
1295 | binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, |
1296 | httpc->local_settings, |
1297 | httpc->local_settings_num); |
1298 | if(!binlen) { |
1299 | failf(conn->data, "nghttp2 unexpectedly failed on pack_settings_payload" ); |
1300 | Curl_add_buffer_free(&req); |
1301 | return CURLE_FAILED_INIT; |
1302 | } |
1303 | conn->proto.httpc.binlen = binlen; |
1304 | |
1305 | result = Curl_base64url_encode(conn->data, (const char *)binsettings, binlen, |
1306 | &base64, &blen); |
1307 | if(result) { |
1308 | Curl_add_buffer_free(&req); |
1309 | return result; |
1310 | } |
1311 | |
1312 | result = Curl_add_bufferf(&req, |
1313 | "Connection: Upgrade, HTTP2-Settings\r\n" |
1314 | "Upgrade: %s\r\n" |
1315 | "HTTP2-Settings: %s\r\n" , |
1316 | NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); |
1317 | free(base64); |
1318 | |
1319 | k->upgr101 = UPGR101_REQUESTED; |
1320 | |
1321 | return result; |
1322 | } |
1323 | |
1324 | /* |
1325 | * Returns nonzero if current HTTP/2 session should be closed. |
1326 | */ |
1327 | static int should_close_session(struct http_conn *httpc) |
1328 | { |
1329 | return httpc->drain_total == 0 && !nghttp2_session_want_read(httpc->h2) && |
1330 | !nghttp2_session_want_write(httpc->h2); |
1331 | } |
1332 | |
1333 | /* |
1334 | * h2_process_pending_input() processes pending input left in |
1335 | * httpc->inbuf. Then, call h2_session_send() to send pending data. |
1336 | * This function returns 0 if it succeeds, or -1 and error code will |
1337 | * be assigned to *err. |
1338 | */ |
1339 | static int h2_process_pending_input(struct connectdata *conn, |
1340 | struct http_conn *httpc, |
1341 | CURLcode *err) |
1342 | { |
1343 | ssize_t nread; |
1344 | char *inbuf; |
1345 | ssize_t rv; |
1346 | struct Curl_easy *data = conn->data; |
1347 | |
1348 | nread = httpc->inbuflen - httpc->nread_inbuf; |
1349 | inbuf = httpc->inbuf + httpc->nread_inbuf; |
1350 | |
1351 | rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread); |
1352 | if(rv < 0) { |
1353 | failf(data, |
1354 | "h2_process_pending_input: nghttp2_session_mem_recv() returned " |
1355 | "%zd:%s\n" , rv, nghttp2_strerror((int)rv)); |
1356 | *err = CURLE_RECV_ERROR; |
1357 | return -1; |
1358 | } |
1359 | |
1360 | if(nread == rv) { |
1361 | H2BUGF(infof(data, |
1362 | "h2_process_pending_input: All data in connection buffer " |
1363 | "processed\n" )); |
1364 | httpc->inbuflen = 0; |
1365 | httpc->nread_inbuf = 0; |
1366 | } |
1367 | else { |
1368 | httpc->nread_inbuf += rv; |
1369 | H2BUGF(infof(data, |
1370 | "h2_process_pending_input: %zu bytes left in connection " |
1371 | "buffer\n" , |
1372 | httpc->inbuflen - httpc->nread_inbuf)); |
1373 | } |
1374 | |
1375 | rv = h2_session_send(data, httpc->h2); |
1376 | if(rv != 0) { |
1377 | *err = CURLE_SEND_ERROR; |
1378 | return -1; |
1379 | } |
1380 | |
1381 | if(should_close_session(httpc)) { |
1382 | H2BUGF(infof(data, |
1383 | "h2_process_pending_input: nothing to do in this session\n" )); |
1384 | if(httpc->error_code) |
1385 | *err = CURLE_HTTP2; |
1386 | else { |
1387 | /* not an error per se, but should still close the connection */ |
1388 | connclose(conn, "GOAWAY received" ); |
1389 | *err = CURLE_OK; |
1390 | } |
1391 | return -1; |
1392 | } |
1393 | |
1394 | return 0; |
1395 | } |
1396 | |
1397 | /* |
1398 | * Called from transfer.c:done_sending when we stop uploading. |
1399 | */ |
1400 | CURLcode Curl_http2_done_sending(struct connectdata *conn) |
1401 | { |
1402 | CURLcode result = CURLE_OK; |
1403 | |
1404 | if((conn->handler == &Curl_handler_http2_ssl) || |
1405 | (conn->handler == &Curl_handler_http2)) { |
1406 | /* make sure this is only attempted for HTTP/2 transfers */ |
1407 | |
1408 | struct HTTP *stream = conn->data->req.protop; |
1409 | |
1410 | if(stream->upload_left) { |
1411 | /* If the stream still thinks there's data left to upload. */ |
1412 | struct http_conn *httpc = &conn->proto.httpc; |
1413 | nghttp2_session *h2 = httpc->h2; |
1414 | |
1415 | stream->upload_left = 0; /* DONE! */ |
1416 | |
1417 | /* resume sending here to trigger the callback to get called again so |
1418 | that it can signal EOF to nghttp2 */ |
1419 | (void)nghttp2_session_resume_data(h2, stream->stream_id); |
1420 | |
1421 | (void)h2_process_pending_input(conn, httpc, &result); |
1422 | } |
1423 | } |
1424 | return result; |
1425 | } |
1426 | |
1427 | static ssize_t http2_handle_stream_close(struct connectdata *conn, |
1428 | struct Curl_easy *data, |
1429 | struct HTTP *stream, CURLcode *err) |
1430 | { |
1431 | char *trailer_pos, *trailer_end; |
1432 | CURLcode result; |
1433 | struct http_conn *httpc = &conn->proto.httpc; |
1434 | |
1435 | if(httpc->pause_stream_id == stream->stream_id) { |
1436 | httpc->pause_stream_id = 0; |
1437 | } |
1438 | |
1439 | drained_transfer(data, httpc); |
1440 | |
1441 | if(httpc->pause_stream_id == 0) { |
1442 | if(h2_process_pending_input(conn, httpc, err) != 0) { |
1443 | return -1; |
1444 | } |
1445 | } |
1446 | |
1447 | DEBUGASSERT(data->state.drain == 0); |
1448 | |
1449 | /* Reset to FALSE to prevent infinite loop in readwrite_data function. */ |
1450 | stream->closed = FALSE; |
1451 | if(httpc->error_code == NGHTTP2_REFUSED_STREAM) { |
1452 | H2BUGF(infof(data, "REFUSED_STREAM (%d), try again on a new connection!\n" , |
1453 | stream->stream_id)); |
1454 | connclose(conn, "REFUSED_STREAM" ); /* don't use this anymore */ |
1455 | data->state.refused_stream = TRUE; |
1456 | *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ |
1457 | return -1; |
1458 | } |
1459 | else if(httpc->error_code != NGHTTP2_NO_ERROR) { |
1460 | failf(data, "HTTP/2 stream %d was not closed cleanly: %s (err %u)" , |
1461 | stream->stream_id, http2_strerror(httpc->error_code), |
1462 | httpc->error_code); |
1463 | *err = CURLE_HTTP2_STREAM; |
1464 | return -1; |
1465 | } |
1466 | |
1467 | if(!stream->bodystarted) { |
1468 | failf(data, "HTTP/2 stream %d was closed cleanly, but before getting " |
1469 | " all response header fields, treated as error" , |
1470 | stream->stream_id); |
1471 | *err = CURLE_HTTP2_STREAM; |
1472 | return -1; |
1473 | } |
1474 | |
1475 | if(stream->trailer_recvbuf && stream->trailer_recvbuf->buffer) { |
1476 | trailer_pos = stream->trailer_recvbuf->buffer; |
1477 | trailer_end = trailer_pos + stream->trailer_recvbuf->size_used; |
1478 | |
1479 | for(; trailer_pos < trailer_end;) { |
1480 | uint32_t n; |
1481 | memcpy(&n, trailer_pos, sizeof(n)); |
1482 | trailer_pos += sizeof(n); |
1483 | |
1484 | result = Curl_client_write(conn, CLIENTWRITE_HEADER, trailer_pos, n); |
1485 | if(result) { |
1486 | *err = result; |
1487 | return -1; |
1488 | } |
1489 | |
1490 | trailer_pos += n + 1; |
1491 | } |
1492 | } |
1493 | |
1494 | stream->close_handled = TRUE; |
1495 | |
1496 | H2BUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close\n" )); |
1497 | return 0; |
1498 | } |
1499 | |
1500 | /* |
1501 | * h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight |
1502 | * and dependency to the peer. It also stores the updated values in the state |
1503 | * struct. |
1504 | */ |
1505 | |
1506 | static void h2_pri_spec(struct Curl_easy *data, |
1507 | nghttp2_priority_spec *pri_spec) |
1508 | { |
1509 | struct HTTP *depstream = (data->set.stream_depends_on? |
1510 | data->set.stream_depends_on->req.protop:NULL); |
1511 | int32_t depstream_id = depstream? depstream->stream_id:0; |
1512 | nghttp2_priority_spec_init(pri_spec, depstream_id, data->set.stream_weight, |
1513 | data->set.stream_depends_e); |
1514 | data->state.stream_weight = data->set.stream_weight; |
1515 | data->state.stream_depends_e = data->set.stream_depends_e; |
1516 | data->state.stream_depends_on = data->set.stream_depends_on; |
1517 | } |
1518 | |
1519 | /* |
1520 | * h2_session_send() checks if there's been an update in the priority / |
1521 | * dependency settings and if so it submits a PRIORITY frame with the updated |
1522 | * info. |
1523 | */ |
1524 | static int h2_session_send(struct Curl_easy *data, |
1525 | nghttp2_session *h2) |
1526 | { |
1527 | struct HTTP *stream = data->req.protop; |
1528 | if((data->set.stream_weight != data->state.stream_weight) || |
1529 | (data->set.stream_depends_e != data->state.stream_depends_e) || |
1530 | (data->set.stream_depends_on != data->state.stream_depends_on) ) { |
1531 | /* send new weight and/or dependency */ |
1532 | nghttp2_priority_spec pri_spec; |
1533 | int rv; |
1534 | |
1535 | h2_pri_spec(data, &pri_spec); |
1536 | |
1537 | H2BUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)\n" , |
1538 | stream->stream_id, data)); |
1539 | DEBUGASSERT(stream->stream_id != -1); |
1540 | rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id, |
1541 | &pri_spec); |
1542 | if(rv) |
1543 | return rv; |
1544 | } |
1545 | |
1546 | return nghttp2_session_send(h2); |
1547 | } |
1548 | |
1549 | static ssize_t http2_recv(struct connectdata *conn, int sockindex, |
1550 | char *mem, size_t len, CURLcode *err) |
1551 | { |
1552 | CURLcode result = CURLE_OK; |
1553 | ssize_t rv; |
1554 | ssize_t nread; |
1555 | struct http_conn *httpc = &conn->proto.httpc; |
1556 | struct Curl_easy *data = conn->data; |
1557 | struct HTTP *stream = data->req.protop; |
1558 | |
1559 | (void)sockindex; /* we always do HTTP2 on sockindex 0 */ |
1560 | |
1561 | if(should_close_session(httpc)) { |
1562 | H2BUGF(infof(data, |
1563 | "http2_recv: nothing to do in this session\n" )); |
1564 | if(conn->bits.close) { |
1565 | /* already marked for closure, return OK and we're done */ |
1566 | *err = CURLE_OK; |
1567 | return 0; |
1568 | } |
1569 | *err = CURLE_HTTP2; |
1570 | return -1; |
1571 | } |
1572 | |
1573 | /* Nullify here because we call nghttp2_session_send() and they |
1574 | might refer to the old buffer. */ |
1575 | stream->upload_mem = NULL; |
1576 | stream->upload_len = 0; |
1577 | |
1578 | /* |
1579 | * At this point 'stream' is just in the Curl_easy the connection |
1580 | * identifies as its owner at this time. |
1581 | */ |
1582 | |
1583 | if(stream->bodystarted && |
1584 | stream->nread_header_recvbuf < stream->header_recvbuf->size_used) { |
1585 | /* If there is body data pending for this stream to return, do that */ |
1586 | size_t left = |
1587 | stream->header_recvbuf->size_used - stream->nread_header_recvbuf; |
1588 | size_t ncopy = CURLMIN(len, left); |
1589 | memcpy(mem, stream->header_recvbuf->buffer + stream->nread_header_recvbuf, |
1590 | ncopy); |
1591 | stream->nread_header_recvbuf += ncopy; |
1592 | |
1593 | H2BUGF(infof(data, "http2_recv: Got %d bytes from header_recvbuf\n" , |
1594 | (int)ncopy)); |
1595 | return ncopy; |
1596 | } |
1597 | |
1598 | H2BUGF(infof(data, "http2_recv: easy %p (stream %u)\n" , |
1599 | data, stream->stream_id)); |
1600 | |
1601 | if((data->state.drain) && stream->memlen) { |
1602 | H2BUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u!! (%p => %p)\n" , |
1603 | stream->memlen, stream->stream_id, |
1604 | stream->mem, mem)); |
1605 | if(mem != stream->mem) { |
1606 | /* if we didn't get the same buffer this time, we must move the data to |
1607 | the beginning */ |
1608 | memmove(mem, stream->mem, stream->memlen); |
1609 | stream->len = len - stream->memlen; |
1610 | stream->mem = mem; |
1611 | } |
1612 | if(httpc->pause_stream_id == stream->stream_id && !stream->pausedata) { |
1613 | /* We have paused nghttp2, but we have no pause data (see |
1614 | on_data_chunk_recv). */ |
1615 | httpc->pause_stream_id = 0; |
1616 | if(h2_process_pending_input(conn, httpc, &result) != 0) { |
1617 | *err = result; |
1618 | return -1; |
1619 | } |
1620 | } |
1621 | } |
1622 | else if(stream->pausedata) { |
1623 | DEBUGASSERT(httpc->pause_stream_id == stream->stream_id); |
1624 | nread = CURLMIN(len, stream->pauselen); |
1625 | memcpy(mem, stream->pausedata, nread); |
1626 | |
1627 | stream->pausedata += nread; |
1628 | stream->pauselen -= nread; |
1629 | |
1630 | infof(data, "%zd data bytes written\n" , nread); |
1631 | if(stream->pauselen == 0) { |
1632 | H2BUGF(infof(data, "Unpaused by stream %u\n" , stream->stream_id)); |
1633 | DEBUGASSERT(httpc->pause_stream_id == stream->stream_id); |
1634 | httpc->pause_stream_id = 0; |
1635 | |
1636 | stream->pausedata = NULL; |
1637 | stream->pauselen = 0; |
1638 | |
1639 | /* When NGHTTP2_ERR_PAUSE is returned from |
1640 | data_source_read_callback, we might not process DATA frame |
1641 | fully. Calling nghttp2_session_mem_recv() again will |
1642 | continue to process DATA frame, but if there is no incoming |
1643 | frames, then we have to call it again with 0-length data. |
1644 | Without this, on_stream_close callback will not be called, |
1645 | and stream could be hanged. */ |
1646 | if(h2_process_pending_input(conn, httpc, &result) != 0) { |
1647 | *err = result; |
1648 | return -1; |
1649 | } |
1650 | } |
1651 | H2BUGF(infof(data, "http2_recv: returns unpaused %zd bytes on stream %u\n" , |
1652 | nread, stream->stream_id)); |
1653 | return nread; |
1654 | } |
1655 | else if(httpc->pause_stream_id) { |
1656 | /* If a stream paused nghttp2_session_mem_recv previously, and has |
1657 | not processed all data, it still refers to the buffer in |
1658 | nghttp2_session. If we call nghttp2_session_mem_recv(), we may |
1659 | overwrite that buffer. To avoid that situation, just return |
1660 | here with CURLE_AGAIN. This could be busy loop since data in |
1661 | socket is not read. But it seems that usually streams are |
1662 | notified with its drain property, and socket is read again |
1663 | quickly. */ |
1664 | if(stream->closed) |
1665 | /* closed overrides paused */ |
1666 | return 0; |
1667 | H2BUGF(infof(data, "stream %x is paused, pause id: %x\n" , |
1668 | stream->stream_id, httpc->pause_stream_id)); |
1669 | *err = CURLE_AGAIN; |
1670 | return -1; |
1671 | } |
1672 | else { |
1673 | char *inbuf; |
1674 | /* remember where to store incoming data for this stream and how big the |
1675 | buffer is */ |
1676 | stream->mem = mem; |
1677 | stream->len = len; |
1678 | stream->memlen = 0; |
1679 | |
1680 | if(httpc->inbuflen == 0) { |
1681 | nread = ((Curl_recv *)httpc->recv_underlying)( |
1682 | conn, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result); |
1683 | |
1684 | if(nread == -1) { |
1685 | if(result != CURLE_AGAIN) |
1686 | failf(data, "Failed receiving HTTP2 data" ); |
1687 | else if(stream->closed) |
1688 | /* received when the stream was already closed! */ |
1689 | return http2_handle_stream_close(conn, data, stream, err); |
1690 | |
1691 | *err = result; |
1692 | return -1; |
1693 | } |
1694 | |
1695 | if(nread == 0) { |
1696 | H2BUGF(infof(data, "end of stream\n" )); |
1697 | *err = CURLE_OK; |
1698 | return 0; |
1699 | } |
1700 | |
1701 | H2BUGF(infof(data, "nread=%zd\n" , nread)); |
1702 | |
1703 | httpc->inbuflen = nread; |
1704 | inbuf = httpc->inbuf; |
1705 | } |
1706 | else { |
1707 | nread = httpc->inbuflen - httpc->nread_inbuf; |
1708 | inbuf = httpc->inbuf + httpc->nread_inbuf; |
1709 | |
1710 | H2BUGF(infof(data, "Use data left in connection buffer, nread=%zd\n" , |
1711 | nread)); |
1712 | } |
1713 | rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread); |
1714 | |
1715 | if(nghttp2_is_fatal((int)rv)) { |
1716 | failf(data, "nghttp2_session_mem_recv() returned %zd:%s\n" , |
1717 | rv, nghttp2_strerror((int)rv)); |
1718 | *err = CURLE_RECV_ERROR; |
1719 | return -1; |
1720 | } |
1721 | H2BUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n" , rv)); |
1722 | if(nread == rv) { |
1723 | H2BUGF(infof(data, "All data in connection buffer processed\n" )); |
1724 | httpc->inbuflen = 0; |
1725 | httpc->nread_inbuf = 0; |
1726 | } |
1727 | else { |
1728 | httpc->nread_inbuf += rv; |
1729 | H2BUGF(infof(data, "%zu bytes left in connection buffer\n" , |
1730 | httpc->inbuflen - httpc->nread_inbuf)); |
1731 | } |
1732 | /* Always send pending frames in nghttp2 session, because |
1733 | nghttp2_session_mem_recv() may queue new frame */ |
1734 | rv = h2_session_send(data, httpc->h2); |
1735 | if(rv != 0) { |
1736 | *err = CURLE_SEND_ERROR; |
1737 | return -1; |
1738 | } |
1739 | |
1740 | if(should_close_session(httpc)) { |
1741 | H2BUGF(infof(data, "http2_recv: nothing to do in this session\n" )); |
1742 | *err = CURLE_HTTP2; |
1743 | return -1; |
1744 | } |
1745 | } |
1746 | if(stream->memlen) { |
1747 | ssize_t retlen = stream->memlen; |
1748 | H2BUGF(infof(data, "http2_recv: returns %zd for stream %u\n" , |
1749 | retlen, stream->stream_id)); |
1750 | stream->memlen = 0; |
1751 | |
1752 | if(httpc->pause_stream_id == stream->stream_id) { |
1753 | /* data for this stream is returned now, but this stream caused a pause |
1754 | already so we need it called again asap */ |
1755 | H2BUGF(infof(data, "Data returned for PAUSED stream %u\n" , |
1756 | stream->stream_id)); |
1757 | } |
1758 | else if(!stream->closed) { |
1759 | drained_transfer(data, httpc); |
1760 | } |
1761 | else |
1762 | /* this stream is closed, trigger a another read ASAP to detect that */ |
1763 | Curl_expire(data, 0, EXPIRE_RUN_NOW); |
1764 | |
1765 | return retlen; |
1766 | } |
1767 | /* If this stream is closed, return 0 to signal the http routine to close |
1768 | the connection */ |
1769 | if(stream->closed) |
1770 | return 0; |
1771 | *err = CURLE_AGAIN; |
1772 | H2BUGF(infof(data, "http2_recv returns AGAIN for stream %u\n" , |
1773 | stream->stream_id)); |
1774 | return -1; |
1775 | } |
1776 | |
1777 | /* Index where :authority header field will appear in request header |
1778 | field list. */ |
1779 | #define AUTHORITY_DST_IDX 3 |
1780 | |
1781 | /* USHRT_MAX is 65535 == 0xffff */ |
1782 | #define HEADER_OVERFLOW(x) \ |
1783 | (x.namelen > 0xffff || x.valuelen > 0xffff - x.namelen) |
1784 | |
1785 | /* |
1786 | * Check header memory for the token "trailers". |
1787 | * Parse the tokens as separated by comma and surrounded by whitespace. |
1788 | * Returns TRUE if found or FALSE if not. |
1789 | */ |
1790 | static bool contains_trailers(const char *p, size_t len) |
1791 | { |
1792 | const char *end = p + len; |
1793 | for(;;) { |
1794 | for(; p != end && (*p == ' ' || *p == '\t'); ++p) |
1795 | ; |
1796 | if(p == end || (size_t)(end - p) < sizeof("trailers" ) - 1) |
1797 | return FALSE; |
1798 | if(strncasecompare("trailers" , p, sizeof("trailers" ) - 1)) { |
1799 | p += sizeof("trailers" ) - 1; |
1800 | for(; p != end && (*p == ' ' || *p == '\t'); ++p) |
1801 | ; |
1802 | if(p == end || *p == ',') |
1803 | return TRUE; |
1804 | } |
1805 | /* skip to next token */ |
1806 | for(; p != end && *p != ','; ++p) |
1807 | ; |
1808 | if(p == end) |
1809 | return FALSE; |
1810 | ++p; |
1811 | } |
1812 | } |
1813 | |
1814 | typedef enum { |
1815 | /* Send header to server */ |
1816 | HEADERINST_FORWARD, |
1817 | /* Don't send header to server */ |
1818 | HEADERINST_IGNORE, |
1819 | /* Discard header, and replace it with "te: trailers" */ |
1820 | HEADERINST_TE_TRAILERS |
1821 | } header_instruction; |
1822 | |
1823 | /* Decides how to treat given header field. */ |
1824 | static header_instruction inspect_header(const char *name, size_t namelen, |
1825 | const char *value, size_t valuelen) { |
1826 | switch(namelen) { |
1827 | case 2: |
1828 | if(!strncasecompare("te" , name, namelen)) |
1829 | return HEADERINST_FORWARD; |
1830 | |
1831 | return contains_trailers(value, valuelen) ? |
1832 | HEADERINST_TE_TRAILERS : HEADERINST_IGNORE; |
1833 | case 7: |
1834 | return strncasecompare("upgrade" , name, namelen) ? |
1835 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
1836 | case 10: |
1837 | return (strncasecompare("connection" , name, namelen) || |
1838 | strncasecompare("keep-alive" , name, namelen)) ? |
1839 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
1840 | case 16: |
1841 | return strncasecompare("proxy-connection" , name, namelen) ? |
1842 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
1843 | case 17: |
1844 | return strncasecompare("transfer-encoding" , name, namelen) ? |
1845 | HEADERINST_IGNORE : HEADERINST_FORWARD; |
1846 | default: |
1847 | return HEADERINST_FORWARD; |
1848 | } |
1849 | } |
1850 | |
1851 | static ssize_t http2_send(struct connectdata *conn, int sockindex, |
1852 | const void *mem, size_t len, CURLcode *err) |
1853 | { |
1854 | /* |
1855 | * Currently, we send request in this function, but this function is also |
1856 | * used to send request body. It would be nice to add dedicated function for |
1857 | * request. |
1858 | */ |
1859 | int rv; |
1860 | struct http_conn *httpc = &conn->proto.httpc; |
1861 | struct HTTP *stream = conn->data->req.protop; |
1862 | nghttp2_nv *nva = NULL; |
1863 | size_t nheader; |
1864 | size_t i; |
1865 | size_t authority_idx; |
1866 | char *hdbuf = (char *)mem; |
1867 | char *end, *line_end; |
1868 | nghttp2_data_provider data_prd; |
1869 | int32_t stream_id; |
1870 | nghttp2_session *h2 = httpc->h2; |
1871 | nghttp2_priority_spec pri_spec; |
1872 | |
1873 | (void)sockindex; |
1874 | |
1875 | H2BUGF(infof(conn->data, "http2_send len=%zu\n" , len)); |
1876 | |
1877 | if(stream->stream_id != -1) { |
1878 | if(stream->close_handled) { |
1879 | infof(conn->data, "stream %d closed\n" , stream->stream_id); |
1880 | *err = CURLE_HTTP2_STREAM; |
1881 | return -1; |
1882 | } |
1883 | else if(stream->closed) { |
1884 | return http2_handle_stream_close(conn, conn->data, stream, err); |
1885 | } |
1886 | /* If stream_id != -1, we have dispatched request HEADERS, and now |
1887 | are going to send or sending request body in DATA frame */ |
1888 | stream->upload_mem = mem; |
1889 | stream->upload_len = len; |
1890 | rv = nghttp2_session_resume_data(h2, stream->stream_id); |
1891 | if(nghttp2_is_fatal(rv)) { |
1892 | *err = CURLE_SEND_ERROR; |
1893 | return -1; |
1894 | } |
1895 | rv = h2_session_send(conn->data, h2); |
1896 | if(nghttp2_is_fatal(rv)) { |
1897 | *err = CURLE_SEND_ERROR; |
1898 | return -1; |
1899 | } |
1900 | len -= stream->upload_len; |
1901 | |
1902 | /* Nullify here because we call nghttp2_session_send() and they |
1903 | might refer to the old buffer. */ |
1904 | stream->upload_mem = NULL; |
1905 | stream->upload_len = 0; |
1906 | |
1907 | if(should_close_session(httpc)) { |
1908 | H2BUGF(infof(conn->data, "http2_send: nothing to do in this session\n" )); |
1909 | *err = CURLE_HTTP2; |
1910 | return -1; |
1911 | } |
1912 | |
1913 | if(stream->upload_left) { |
1914 | /* we are sure that we have more data to send here. Calling the |
1915 | following API will make nghttp2_session_want_write() return |
1916 | nonzero if remote window allows it, which then libcurl checks |
1917 | socket is writable or not. See http2_perform_getsock(). */ |
1918 | nghttp2_session_resume_data(h2, stream->stream_id); |
1919 | } |
1920 | |
1921 | H2BUGF(infof(conn->data, "http2_send returns %zu for stream %u\n" , len, |
1922 | stream->stream_id)); |
1923 | return len; |
1924 | } |
1925 | |
1926 | /* Calculate number of headers contained in [mem, mem + len) */ |
1927 | /* Here, we assume the curl http code generate *correct* HTTP header |
1928 | field block */ |
1929 | nheader = 0; |
1930 | for(i = 1; i < len; ++i) { |
1931 | if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') { |
1932 | ++nheader; |
1933 | ++i; |
1934 | } |
1935 | } |
1936 | if(nheader < 2) |
1937 | goto fail; |
1938 | |
1939 | /* We counted additional 2 \r\n in the first and last line. We need 3 |
1940 | new headers: :method, :path and :scheme. Therefore we need one |
1941 | more space. */ |
1942 | nheader += 1; |
1943 | nva = malloc(sizeof(nghttp2_nv) * nheader); |
1944 | if(nva == NULL) { |
1945 | *err = CURLE_OUT_OF_MEMORY; |
1946 | return -1; |
1947 | } |
1948 | |
1949 | /* Extract :method, :path from request line |
1950 | We do line endings with CRLF so checking for CR is enough */ |
1951 | line_end = memchr(hdbuf, '\r', len); |
1952 | if(!line_end) |
1953 | goto fail; |
1954 | |
1955 | /* Method does not contain spaces */ |
1956 | end = memchr(hdbuf, ' ', line_end - hdbuf); |
1957 | if(!end || end == hdbuf) |
1958 | goto fail; |
1959 | nva[0].name = (unsigned char *)":method" ; |
1960 | nva[0].namelen = strlen((char *)nva[0].name); |
1961 | nva[0].value = (unsigned char *)hdbuf; |
1962 | nva[0].valuelen = (size_t)(end - hdbuf); |
1963 | nva[0].flags = NGHTTP2_NV_FLAG_NONE; |
1964 | if(HEADER_OVERFLOW(nva[0])) { |
1965 | failf(conn->data, "Failed sending HTTP request: Header overflow" ); |
1966 | goto fail; |
1967 | } |
1968 | |
1969 | hdbuf = end + 1; |
1970 | |
1971 | /* Path may contain spaces so scan backwards */ |
1972 | end = NULL; |
1973 | for(i = (size_t)(line_end - hdbuf); i; --i) { |
1974 | if(hdbuf[i - 1] == ' ') { |
1975 | end = &hdbuf[i - 1]; |
1976 | break; |
1977 | } |
1978 | } |
1979 | if(!end || end == hdbuf) |
1980 | goto fail; |
1981 | nva[1].name = (unsigned char *)":path" ; |
1982 | nva[1].namelen = strlen((char *)nva[1].name); |
1983 | nva[1].value = (unsigned char *)hdbuf; |
1984 | nva[1].valuelen = (size_t)(end - hdbuf); |
1985 | nva[1].flags = NGHTTP2_NV_FLAG_NONE; |
1986 | if(HEADER_OVERFLOW(nva[1])) { |
1987 | failf(conn->data, "Failed sending HTTP request: Header overflow" ); |
1988 | goto fail; |
1989 | } |
1990 | |
1991 | nva[2].name = (unsigned char *)":scheme" ; |
1992 | nva[2].namelen = strlen((char *)nva[2].name); |
1993 | if(conn->handler->flags & PROTOPT_SSL) |
1994 | nva[2].value = (unsigned char *)"https" ; |
1995 | else |
1996 | nva[2].value = (unsigned char *)"http" ; |
1997 | nva[2].valuelen = strlen((char *)nva[2].value); |
1998 | nva[2].flags = NGHTTP2_NV_FLAG_NONE; |
1999 | if(HEADER_OVERFLOW(nva[2])) { |
2000 | failf(conn->data, "Failed sending HTTP request: Header overflow" ); |
2001 | goto fail; |
2002 | } |
2003 | |
2004 | authority_idx = 0; |
2005 | i = 3; |
2006 | while(i < nheader) { |
2007 | size_t hlen; |
2008 | |
2009 | hdbuf = line_end + 2; |
2010 | |
2011 | /* check for next CR, but only within the piece of data left in the given |
2012 | buffer */ |
2013 | line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem)); |
2014 | if(!line_end || (line_end == hdbuf)) |
2015 | goto fail; |
2016 | |
2017 | /* header continuation lines are not supported */ |
2018 | if(*hdbuf == ' ' || *hdbuf == '\t') |
2019 | goto fail; |
2020 | |
2021 | for(end = hdbuf; end < line_end && *end != ':'; ++end) |
2022 | ; |
2023 | if(end == hdbuf || end == line_end) |
2024 | goto fail; |
2025 | hlen = end - hdbuf; |
2026 | |
2027 | if(hlen == 4 && strncasecompare("host" , hdbuf, 4)) { |
2028 | authority_idx = i; |
2029 | nva[i].name = (unsigned char *)":authority" ; |
2030 | nva[i].namelen = strlen((char *)nva[i].name); |
2031 | } |
2032 | else { |
2033 | nva[i].namelen = (size_t)(end - hdbuf); |
2034 | /* Lower case the header name for HTTP/2 */ |
2035 | Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen); |
2036 | nva[i].name = (unsigned char *)hdbuf; |
2037 | } |
2038 | hdbuf = end + 1; |
2039 | while(*hdbuf == ' ' || *hdbuf == '\t') |
2040 | ++hdbuf; |
2041 | end = line_end; |
2042 | |
2043 | switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf, |
2044 | end - hdbuf)) { |
2045 | case HEADERINST_IGNORE: |
2046 | /* skip header fields prohibited by HTTP/2 specification. */ |
2047 | --nheader; |
2048 | continue; |
2049 | case HEADERINST_TE_TRAILERS: |
2050 | nva[i].value = (uint8_t*)"trailers" ; |
2051 | nva[i].valuelen = sizeof("trailers" ) - 1; |
2052 | break; |
2053 | default: |
2054 | nva[i].value = (unsigned char *)hdbuf; |
2055 | nva[i].valuelen = (size_t)(end - hdbuf); |
2056 | } |
2057 | |
2058 | nva[i].flags = NGHTTP2_NV_FLAG_NONE; |
2059 | if(HEADER_OVERFLOW(nva[i])) { |
2060 | failf(conn->data, "Failed sending HTTP request: Header overflow" ); |
2061 | goto fail; |
2062 | } |
2063 | ++i; |
2064 | } |
2065 | |
2066 | /* :authority must come before non-pseudo header fields */ |
2067 | if(authority_idx != 0 && authority_idx != AUTHORITY_DST_IDX) { |
2068 | nghttp2_nv authority = nva[authority_idx]; |
2069 | for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) { |
2070 | nva[i] = nva[i - 1]; |
2071 | } |
2072 | nva[i] = authority; |
2073 | } |
2074 | |
2075 | /* Warn stream may be rejected if cumulative length of headers is too large. |
2076 | It appears nghttp2 will not send a header frame larger than 64KB. */ |
2077 | #define MAX_ACC 60000 /* <64KB to account for some overhead */ |
2078 | { |
2079 | size_t acc = 0; |
2080 | |
2081 | for(i = 0; i < nheader; ++i) { |
2082 | acc += nva[i].namelen + nva[i].valuelen; |
2083 | |
2084 | H2BUGF(infof(conn->data, "h2 header: %.*s:%.*s\n" , |
2085 | nva[i].namelen, nva[i].name, |
2086 | nva[i].valuelen, nva[i].value)); |
2087 | } |
2088 | |
2089 | if(acc > MAX_ACC) { |
2090 | infof(conn->data, "http2_send: Warning: The cumulative length of all " |
2091 | "headers exceeds %zu bytes and that could cause the " |
2092 | "stream to be rejected.\n" , MAX_ACC); |
2093 | } |
2094 | } |
2095 | |
2096 | h2_pri_spec(conn->data, &pri_spec); |
2097 | |
2098 | switch(conn->data->set.httpreq) { |
2099 | case HTTPREQ_POST: |
2100 | case HTTPREQ_POST_FORM: |
2101 | case HTTPREQ_POST_MIME: |
2102 | case HTTPREQ_PUT: |
2103 | if(conn->data->state.infilesize != -1) |
2104 | stream->upload_left = conn->data->state.infilesize; |
2105 | else |
2106 | /* data sending without specifying the data amount up front */ |
2107 | stream->upload_left = -1; /* unknown, but not zero */ |
2108 | |
2109 | data_prd.read_callback = data_source_read_callback; |
2110 | data_prd.source.ptr = NULL; |
2111 | stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, |
2112 | &data_prd, conn->data); |
2113 | break; |
2114 | default: |
2115 | stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader, |
2116 | NULL, conn->data); |
2117 | } |
2118 | |
2119 | Curl_safefree(nva); |
2120 | |
2121 | if(stream_id < 0) { |
2122 | H2BUGF(infof(conn->data, "http2_send() send error\n" )); |
2123 | *err = CURLE_SEND_ERROR; |
2124 | return -1; |
2125 | } |
2126 | |
2127 | infof(conn->data, "Using Stream ID: %x (easy handle %p)\n" , |
2128 | stream_id, (void *)conn->data); |
2129 | stream->stream_id = stream_id; |
2130 | |
2131 | /* this does not call h2_session_send() since there can not have been any |
2132 | * priority upodate since the nghttp2_submit_request() call above */ |
2133 | rv = nghttp2_session_send(h2); |
2134 | |
2135 | if(rv != 0) { |
2136 | *err = CURLE_SEND_ERROR; |
2137 | return -1; |
2138 | } |
2139 | |
2140 | if(should_close_session(httpc)) { |
2141 | H2BUGF(infof(conn->data, "http2_send: nothing to do in this session\n" )); |
2142 | *err = CURLE_HTTP2; |
2143 | return -1; |
2144 | } |
2145 | |
2146 | /* If whole HEADERS frame was sent off to the underlying socket, the nghttp2 |
2147 | library calls data_source_read_callback. But only it found that no data |
2148 | available, so it deferred the DATA transmission. Which means that |
2149 | nghttp2_session_want_write() returns 0 on http2_perform_getsock(), which |
2150 | results that no writable socket check is performed. To workaround this, |
2151 | we issue nghttp2_session_resume_data() here to bring back DATA |
2152 | transmission from deferred state. */ |
2153 | nghttp2_session_resume_data(h2, stream->stream_id); |
2154 | |
2155 | return len; |
2156 | |
2157 | fail: |
2158 | free(nva); |
2159 | *err = CURLE_SEND_ERROR; |
2160 | return -1; |
2161 | } |
2162 | |
2163 | CURLcode Curl_http2_setup(struct connectdata *conn) |
2164 | { |
2165 | CURLcode result; |
2166 | struct http_conn *httpc = &conn->proto.httpc; |
2167 | struct HTTP *stream = conn->data->req.protop; |
2168 | |
2169 | stream->stream_id = -1; |
2170 | |
2171 | if(!stream->header_recvbuf) { |
2172 | stream->header_recvbuf = Curl_add_buffer_init(); |
2173 | if(!stream->header_recvbuf) |
2174 | return CURLE_OUT_OF_MEMORY; |
2175 | } |
2176 | |
2177 | if((conn->handler == &Curl_handler_http2_ssl) || |
2178 | (conn->handler == &Curl_handler_http2)) |
2179 | return CURLE_OK; /* already done */ |
2180 | |
2181 | if(conn->handler->flags & PROTOPT_SSL) |
2182 | conn->handler = &Curl_handler_http2_ssl; |
2183 | else |
2184 | conn->handler = &Curl_handler_http2; |
2185 | |
2186 | result = http2_init(conn); |
2187 | if(result) { |
2188 | Curl_add_buffer_free(&stream->header_recvbuf); |
2189 | return result; |
2190 | } |
2191 | |
2192 | infof(conn->data, "Using HTTP2, server supports multi-use\n" ); |
2193 | stream->upload_left = 0; |
2194 | stream->upload_mem = NULL; |
2195 | stream->upload_len = 0; |
2196 | |
2197 | httpc->inbuflen = 0; |
2198 | httpc->nread_inbuf = 0; |
2199 | |
2200 | httpc->pause_stream_id = 0; |
2201 | httpc->drain_total = 0; |
2202 | |
2203 | conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ |
2204 | conn->httpversion = 20; |
2205 | conn->bundle->multiuse = BUNDLE_MULTIPLEX; |
2206 | |
2207 | infof(conn->data, "Connection state changed (HTTP/2 confirmed)\n" ); |
2208 | multi_connchanged(conn->data->multi); |
2209 | |
2210 | return CURLE_OK; |
2211 | } |
2212 | |
2213 | CURLcode Curl_http2_switched(struct connectdata *conn, |
2214 | const char *mem, size_t nread) |
2215 | { |
2216 | CURLcode result; |
2217 | struct http_conn *httpc = &conn->proto.httpc; |
2218 | int rv; |
2219 | ssize_t nproc; |
2220 | struct Curl_easy *data = conn->data; |
2221 | struct HTTP *stream = conn->data->req.protop; |
2222 | |
2223 | result = Curl_http2_setup(conn); |
2224 | if(result) |
2225 | return result; |
2226 | |
2227 | httpc->recv_underlying = conn->recv[FIRSTSOCKET]; |
2228 | httpc->send_underlying = conn->send[FIRSTSOCKET]; |
2229 | conn->recv[FIRSTSOCKET] = http2_recv; |
2230 | conn->send[FIRSTSOCKET] = http2_send; |
2231 | |
2232 | if(conn->data->req.upgr101 == UPGR101_RECEIVED) { |
2233 | /* stream 1 is opened implicitly on upgrade */ |
2234 | stream->stream_id = 1; |
2235 | /* queue SETTINGS frame (again) */ |
2236 | rv = nghttp2_session_upgrade(httpc->h2, httpc->binsettings, |
2237 | httpc->binlen, NULL); |
2238 | if(rv != 0) { |
2239 | failf(data, "nghttp2_session_upgrade() failed: %s(%d)" , |
2240 | nghttp2_strerror(rv), rv); |
2241 | return CURLE_HTTP2; |
2242 | } |
2243 | |
2244 | rv = nghttp2_session_set_stream_user_data(httpc->h2, |
2245 | stream->stream_id, |
2246 | data); |
2247 | if(rv) { |
2248 | infof(data, "http/2: failed to set user_data for stream %d!\n" , |
2249 | stream->stream_id); |
2250 | DEBUGASSERT(0); |
2251 | } |
2252 | } |
2253 | else { |
2254 | populate_settings(conn, httpc); |
2255 | |
2256 | /* stream ID is unknown at this point */ |
2257 | stream->stream_id = -1; |
2258 | rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, |
2259 | httpc->local_settings, |
2260 | httpc->local_settings_num); |
2261 | if(rv != 0) { |
2262 | failf(data, "nghttp2_submit_settings() failed: %s(%d)" , |
2263 | nghttp2_strerror(rv), rv); |
2264 | return CURLE_HTTP2; |
2265 | } |
2266 | } |
2267 | |
2268 | #ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE |
2269 | rv = nghttp2_session_set_local_window_size(httpc->h2, NGHTTP2_FLAG_NONE, 0, |
2270 | HTTP2_HUGE_WINDOW_SIZE); |
2271 | if(rv != 0) { |
2272 | failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)" , |
2273 | nghttp2_strerror(rv), rv); |
2274 | return CURLE_HTTP2; |
2275 | } |
2276 | #endif |
2277 | |
2278 | /* we are going to copy mem to httpc->inbuf. This is required since |
2279 | mem is part of buffer pointed by stream->mem, and callbacks |
2280 | called by nghttp2_session_mem_recv() will write stream specific |
2281 | data into stream->mem, overwriting data already there. */ |
2282 | if(H2_BUFSIZE < nread) { |
2283 | failf(data, "connection buffer size is too small to store data following " |
2284 | "HTTP Upgrade response header: buflen=%zu, datalen=%zu" , |
2285 | H2_BUFSIZE, nread); |
2286 | return CURLE_HTTP2; |
2287 | } |
2288 | |
2289 | infof(conn->data, "Copying HTTP/2 data in stream buffer to connection buffer" |
2290 | " after upgrade: len=%zu\n" , |
2291 | nread); |
2292 | |
2293 | if(nread) |
2294 | memcpy(httpc->inbuf, mem, nread); |
2295 | httpc->inbuflen = nread; |
2296 | |
2297 | nproc = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)httpc->inbuf, |
2298 | httpc->inbuflen); |
2299 | |
2300 | if(nghttp2_is_fatal((int)nproc)) { |
2301 | failf(data, "nghttp2_session_mem_recv() failed: %s(%d)" , |
2302 | nghttp2_strerror((int)nproc), (int)nproc); |
2303 | return CURLE_HTTP2; |
2304 | } |
2305 | |
2306 | H2BUGF(infof(data, "nghttp2_session_mem_recv() returns %zd\n" , nproc)); |
2307 | |
2308 | if((ssize_t)nread == nproc) { |
2309 | httpc->inbuflen = 0; |
2310 | httpc->nread_inbuf = 0; |
2311 | } |
2312 | else { |
2313 | httpc->nread_inbuf += nproc; |
2314 | } |
2315 | |
2316 | /* Try to send some frames since we may read SETTINGS already. */ |
2317 | rv = h2_session_send(data, httpc->h2); |
2318 | |
2319 | if(rv != 0) { |
2320 | failf(data, "nghttp2_session_send() failed: %s(%d)" , |
2321 | nghttp2_strerror(rv), rv); |
2322 | return CURLE_HTTP2; |
2323 | } |
2324 | |
2325 | if(should_close_session(httpc)) { |
2326 | H2BUGF(infof(data, |
2327 | "nghttp2_session_send(): nothing to do in this session\n" )); |
2328 | return CURLE_HTTP2; |
2329 | } |
2330 | |
2331 | return CURLE_OK; |
2332 | } |
2333 | |
2334 | CURLcode Curl_http2_add_child(struct Curl_easy *parent, |
2335 | struct Curl_easy *child, |
2336 | bool exclusive) |
2337 | { |
2338 | if(parent) { |
2339 | struct Curl_http2_dep **tail; |
2340 | struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep)); |
2341 | if(!dep) |
2342 | return CURLE_OUT_OF_MEMORY; |
2343 | dep->data = child; |
2344 | |
2345 | if(parent->set.stream_dependents && exclusive) { |
2346 | struct Curl_http2_dep *node = parent->set.stream_dependents; |
2347 | while(node) { |
2348 | node->data->set.stream_depends_on = child; |
2349 | node = node->next; |
2350 | } |
2351 | |
2352 | tail = &child->set.stream_dependents; |
2353 | while(*tail) |
2354 | tail = &(*tail)->next; |
2355 | |
2356 | DEBUGASSERT(!*tail); |
2357 | *tail = parent->set.stream_dependents; |
2358 | parent->set.stream_dependents = 0; |
2359 | } |
2360 | |
2361 | tail = &parent->set.stream_dependents; |
2362 | while(*tail) { |
2363 | (*tail)->data->set.stream_depends_e = FALSE; |
2364 | tail = &(*tail)->next; |
2365 | } |
2366 | |
2367 | DEBUGASSERT(!*tail); |
2368 | *tail = dep; |
2369 | } |
2370 | |
2371 | child->set.stream_depends_on = parent; |
2372 | child->set.stream_depends_e = exclusive; |
2373 | return CURLE_OK; |
2374 | } |
2375 | |
2376 | void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child) |
2377 | { |
2378 | struct Curl_http2_dep *last = 0; |
2379 | struct Curl_http2_dep *data = parent->set.stream_dependents; |
2380 | DEBUGASSERT(child->set.stream_depends_on == parent); |
2381 | |
2382 | while(data && data->data != child) { |
2383 | last = data; |
2384 | data = data->next; |
2385 | } |
2386 | |
2387 | DEBUGASSERT(data); |
2388 | |
2389 | if(data) { |
2390 | if(last) { |
2391 | last->next = data->next; |
2392 | } |
2393 | else { |
2394 | parent->set.stream_dependents = data->next; |
2395 | } |
2396 | free(data); |
2397 | } |
2398 | |
2399 | child->set.stream_depends_on = 0; |
2400 | child->set.stream_depends_e = FALSE; |
2401 | } |
2402 | |
2403 | void Curl_http2_cleanup_dependencies(struct Curl_easy *data) |
2404 | { |
2405 | while(data->set.stream_dependents) { |
2406 | struct Curl_easy *tmp = data->set.stream_dependents->data; |
2407 | Curl_http2_remove_child(data, tmp); |
2408 | if(data->set.stream_depends_on) |
2409 | Curl_http2_add_child(data->set.stream_depends_on, tmp, FALSE); |
2410 | } |
2411 | |
2412 | if(data->set.stream_depends_on) |
2413 | Curl_http2_remove_child(data->set.stream_depends_on, data); |
2414 | } |
2415 | |
2416 | /* Only call this function for a transfer that already got a HTTP/2 |
2417 | CURLE_HTTP2_STREAM error! */ |
2418 | bool Curl_h2_http_1_1_error(struct connectdata *conn) |
2419 | { |
2420 | struct http_conn *httpc = &conn->proto.httpc; |
2421 | return (httpc->error_code == NGHTTP2_HTTP_1_1_REQUIRED); |
2422 | } |
2423 | |
2424 | #else /* !USE_NGHTTP2 */ |
2425 | |
2426 | /* Satisfy external references even if http2 is not compiled in. */ |
2427 | #include <curl/curl.h> |
2428 | |
2429 | char *(struct curl_pushheaders *h, size_t num) |
2430 | { |
2431 | (void) h; |
2432 | (void) num; |
2433 | return NULL; |
2434 | } |
2435 | |
2436 | char *(struct curl_pushheaders *h, const char *) |
2437 | { |
2438 | (void) h; |
2439 | (void) header; |
2440 | return NULL; |
2441 | } |
2442 | |
2443 | #endif /* USE_NGHTTP2 */ |
2444 | |