Coverage Report

Created: 2026-08-31 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libsoup/libsoup/http2/soup-client-message-io-http2.c
Line
Count
Source
1
/* soup-message-io-http2.c
2
 *
3
 * Copyright 2021 Igalia S.L.
4
 *
5
 * This file is free software; you can redistribute it and/or modify it
6
 * under the terms of the GNU Lesser General Public License as
7
 * published by the Free Software Foundation; either version 2 of the
8
 * License, or (at your option) any later version.
9
 *
10
 * This file is distributed in the hope that it will be useful, but
11
 * WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
16
 * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 *
18
 * SPDX-License-Identifier: LGPL-2.0-or-later
19
 */
20
21
#ifdef HAVE_CONFIG_H
22
#include "config.h"
23
#endif
24
25
#undef G_LOG_DOMAIN
26
0
#define G_LOG_DOMAIN "libsoup-http2"
27
28
#include <glib.h>
29
#include <glib/gi18n-lib.h>
30
31
#include "soup-client-message-io-http2.h"
32
33
#include "soup-body-input-stream.h"
34
#include "soup-message-metrics-private.h"
35
#include "soup-message-headers-private.h"
36
#include "soup-message-private.h"
37
#include "soup-message-io-source.h"
38
#include "soup-message-queue-item.h"
39
#include "content-sniffer/soup-content-sniffer-stream.h"
40
#include "soup-client-input-stream.h"
41
#include "soup-logger-private.h"
42
#include "soup-uri-utils-private.h"
43
#include "soup-http2-utils.h"
44
45
#include "content-decoder/soup-content-decoder.h"
46
#include "soup-body-input-stream-http2.h"
47
48
0
#define FRAME_HEADER_SIZE 9
49
50
typedef struct {
51
        SoupClientMessageIO iface;
52
53
        GThread *owner;
54
        gboolean async;
55
        GWeakRef conn;
56
        GIOStream *stream;
57
        GInputStream *istream;
58
        GOutputStream *ostream;
59
        guint64 connection_id;
60
61
        GError *error;
62
        GSource *read_source;
63
        GSource *write_source;
64
        GSource *write_idle_source;
65
66
        GHashTable *messages;
67
        GHashTable *closed_messages;
68
        GList *pending_io_messages;
69
70
        nghttp2_session *session;
71
72
        /* Owned by nghttp2 */
73
        guint8 *write_buffer;
74
        gssize write_buffer_size;
75
        gssize written_bytes;
76
77
        gboolean is_shutdown;
78
        GTask *close_task;
79
        gboolean session_terminated;
80
        gboolean goaway_sent;
81
        gboolean ever_used;
82
83
        guint in_callback;
84
} SoupClientMessageIOHTTP2;
85
86
typedef struct {
87
        SoupMessageQueueItem *item;
88
        SoupMessage *msg;
89
        SoupMessageMetrics *metrics;
90
        GInputStream *decoded_data_istream;
91
        GInputStream *body_istream;
92
        GTask *task;
93
        gboolean in_io_try_sniff_content;
94
95
        /* Request body */
96
        SoupLogger *logger;
97
        gssize request_body_bytes_to_write;
98
99
        /* Pollable data sources */
100
        GSource *data_source_poll;
101
102
        /* Non-pollable data sources */
103
        GByteArray *data_source_buffer;
104
        GError *data_source_error;
105
        gboolean data_source_eof;
106
        GCancellable *data_source_cancellable;
107
        GCancellable *data_source_message_cancellable;
108
        gulong data_source_cancellable_id;
109
110
        SoupClientMessageIOHTTP2 *io; /* Unowned */
111
        SoupMessageIOCompletionFn completion_cb;
112
        gpointer completion_data;
113
        SoupHTTP2IOState state;
114
        GError *error;
115
        uint32_t http2_error;
116
        gboolean paused;
117
        guint32 stream_id;
118
        gboolean can_be_restarted;
119
        gboolean expect_continue;
120
        GSource *check_status_idle_source;
121
} SoupHTTP2MessageData;
122
123
static void soup_client_message_io_http2_finished (SoupClientMessageIO *iface, SoupMessage *msg);
124
static ssize_t on_data_source_read_callback (nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length, uint32_t *data_flags, nghttp2_data_source *source, void *user_data);
125
126
G_GNUC_PRINTF(3, 0)
127
static void
128
h2_debug (SoupClientMessageIOHTTP2   *io,
129
          SoupHTTP2MessageData       *data,
130
          const char                 *format,
131
          ...)
132
0
{
133
0
        va_list args;
134
0
        char *message;
135
0
        guint32 stream_id = 0;
136
137
0
        if (g_log_writer_default_would_drop (G_LOG_LEVEL_DEBUG, G_LOG_DOMAIN))
138
0
                return;
139
140
0
  va_start (args, format);
141
0
  message = g_strdup_vprintf (format, args);
142
0
  va_end (args);
143
144
0
        if (data)
145
0
                stream_id = data->stream_id;
146
147
0
        g_assert (io);
148
0
        g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, "[CLIENT] [C%" G_GUINT64_FORMAT "-S%u] [%s] %s", io->connection_id, stream_id, data ? soup_http2_io_state_to_string (data->state) : "-", message);
149
150
0
        g_free (message);
151
0
}
152
153
static SoupClientMessageIOHTTP2 *
154
get_io_data (SoupMessage *msg)
155
0
{
156
0
        return (SoupClientMessageIOHTTP2 *)soup_message_get_io_data (msg);
157
0
}
158
159
static int
160
get_data_io_priority (SoupHTTP2MessageData *data)
161
0
{
162
0
  if (!data->item->task)
163
0
    return G_PRIORITY_DEFAULT;
164
165
0
  return g_task_get_priority (data->item->task);
166
0
}
167
168
static void
169
set_error_for_data (SoupHTTP2MessageData *data,
170
                    GError               *error)
171
0
{
172
0
        h2_debug (data->io, data, "[SESSION] Error: %s", error->message);
173
174
        /* First error is probably the one we want. */
175
0
        if (!data->error)
176
0
                data->error = error;
177
0
        else
178
0
                g_error_free (error);
179
0
}
180
181
static void
182
set_http2_error_for_data (SoupHTTP2MessageData *data,
183
                          uint32_t              error_code)
184
0
{
185
0
        h2_debug (data->io, data, "[SESSION] Error: %s", nghttp2_http2_strerror (error_code));
186
187
0
        if (data->error)
188
0
                return;
189
190
0
        data->http2_error = error_code;
191
0
        data->error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
192
0
                                   "HTTP/2 Error: %s", nghttp2_http2_strerror (error_code));
193
0
}
194
195
static void
196
set_io_error (SoupClientMessageIOHTTP2 *io,
197
              GError                   *error)
198
0
{
199
0
        h2_debug (io, NULL, "[SESSION] IO error: %s", error->message);
200
201
0
        if (!io->error)
202
0
                io->error = error;
203
0
        else
204
0
                g_error_free (error);
205
206
0
        if (io->close_task && !io->goaway_sent) {
207
0
                g_task_return_boolean (io->close_task, TRUE);
208
0
                g_clear_object (&io->close_task);
209
0
        }
210
0
}
211
212
static void
213
advance_state_from (SoupHTTP2MessageData *data,
214
                    SoupHTTP2IOState      from,
215
                    SoupHTTP2IOState      to)
216
0
{
217
0
        if (data->state != from) {
218
0
                g_warning ("Unexpected state changed %s -> %s, expected to be from %s",
219
0
                           soup_http2_io_state_to_string (data->state), soup_http2_io_state_to_string (to),
220
0
                           soup_http2_io_state_to_string (from));
221
0
        }
222
223
        /* State never goes backwards */
224
0
        if (to < data->state) {
225
0
                g_warning ("Unexpected state changed %s -> %s, expected %s -> %s\n",
226
0
                           soup_http2_io_state_to_string (data->state), soup_http2_io_state_to_string (to),
227
0
                           soup_http2_io_state_to_string (from), soup_http2_io_state_to_string (to));
228
0
                return;
229
0
        }
230
231
0
        h2_debug (data->io, data, "[SESSION] State %s -> %s",
232
0
                  soup_http2_io_state_to_string (data->state), soup_http2_io_state_to_string (to));
233
0
        data->state = to;
234
0
}
235
236
static gboolean
237
soup_http2_message_data_can_be_restarted (SoupHTTP2MessageData *data,
238
                                          GError               *error)
239
0
{
240
0
        if (data->can_be_restarted)
241
0
                return TRUE;
242
243
0
        return data->state < STATE_READ_DATA_START &&
244
0
                data->io->ever_used &&
245
0
                !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT) &&
246
0
                !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) &&
247
0
                !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
248
0
                error->domain != G_TLS_ERROR &&
249
0
                data->http2_error == NGHTTP2_NO_ERROR &&
250
0
                SOUP_METHOD_IS_IDEMPOTENT (soup_message_get_method (data->msg));
251
0
}
252
253
static void
254
soup_http2_message_data_destroy_check_status_idle_source (SoupHTTP2MessageData *data)
255
0
{
256
0
        if (!data->check_status_idle_source)
257
0
                return;
258
259
0
        g_source_destroy (data->check_status_idle_source);
260
0
        g_clear_pointer (&data->check_status_idle_source, g_source_unref);
261
0
}
262
263
static void
264
soup_http2_message_data_check_status (SoupHTTP2MessageData *data)
265
0
{
266
0
        SoupClientMessageIOHTTP2 *io = data->io;
267
0
        SoupMessage *msg = data->msg;
268
0
        GTask *task = data->task;
269
0
        GError *error = NULL;
270
271
0
        soup_http2_message_data_destroy_check_status_idle_source (data);
272
273
0
        if (!task)
274
0
                return;
275
276
0
        if (g_cancellable_set_error_if_cancelled (g_task_get_cancellable (task), &error)) {
277
0
                io->pending_io_messages = g_list_remove (io->pending_io_messages, data);
278
0
                data->task = NULL;
279
0
                soup_client_message_io_http2_finished ((SoupClientMessageIO *)io, msg);
280
0
                g_task_return_error (task, error);
281
0
                g_object_unref (task);
282
0
                return;
283
0
        }
284
285
0
        if (data->paused)
286
0
                return;
287
288
0
        if (io->error && !data->error)
289
0
                data->error = g_error_copy (io->error);
290
291
0
        if (data->error) {
292
0
                GError *error = g_steal_pointer (&data->error);
293
294
0
                if (soup_http2_message_data_can_be_restarted (data, error))
295
0
                        data->item->state = SOUP_MESSAGE_RESTARTING;
296
0
                else
297
0
                        soup_message_set_metrics_timestamp (data->msg, SOUP_MESSAGE_METRICS_RESPONSE_END);
298
0
                io->pending_io_messages = g_list_remove (io->pending_io_messages, data);
299
0
                data->task = NULL;
300
0
                soup_client_message_io_http2_finished ((SoupClientMessageIO *)io, msg);
301
302
0
                g_task_return_error (task, error);
303
0
                g_object_unref (task);
304
0
                return;
305
0
        }
306
307
0
        if (data->state == STATE_READ_DATA_START && !soup_message_has_content_sniffer (msg))
308
0
                advance_state_from (data, STATE_READ_DATA_START, STATE_READ_DATA);
309
310
0
        if (data->state < STATE_READ_DATA)
311
0
                return;
312
313
0
        io->pending_io_messages = g_list_remove (io->pending_io_messages, data);
314
0
        data->task = NULL;
315
0
        g_task_return_boolean (task, TRUE);
316
0
        g_object_unref (task);
317
0
}
318
319
static gboolean
320
check_status_idle_source_cb (SoupHTTP2MessageData *data)
321
0
{
322
0
        g_clear_pointer (&data->check_status_idle_source, g_source_unref);
323
0
        soup_http2_message_data_check_status (data);
324
0
        return G_SOURCE_REMOVE;
325
0
}
326
327
static void
328
soup_http2_message_data_check_status_in_idle (SoupHTTP2MessageData *data)
329
0
{
330
0
        if (data->check_status_idle_source)
331
0
                return;
332
333
0
        data->check_status_idle_source = g_idle_source_new ();
334
0
        g_source_set_static_name (data->check_status_idle_source, "Soup HTTP/2 check message data status");
335
0
        g_source_set_priority (data->check_status_idle_source, G_PRIORITY_DEFAULT);
336
0
        g_source_set_callback (data->check_status_idle_source, (GSourceFunc)check_status_idle_source_cb, data, NULL);
337
0
        g_source_attach (data->check_status_idle_source, g_main_context_get_thread_default ());
338
0
}
339
340
static gboolean
341
io_write (SoupClientMessageIOHTTP2 *io,
342
          gboolean                  blocking,
343
          GCancellable             *cancellable,
344
          GError                  **error)
345
0
{
346
        /* We must write all of nghttp2's buffer before we ask for more */
347
0
        if (io->written_bytes == io->write_buffer_size)
348
0
                io->write_buffer = NULL;
349
350
0
        if (io->write_buffer == NULL) {
351
0
                io->written_bytes = 0;
352
0
                g_warn_if_fail (io->in_callback == 0);
353
0
                io->write_buffer_size = nghttp2_session_mem_send (io->session, (const guint8**)&io->write_buffer);
354
0
                NGCHECK (io->write_buffer_size);
355
0
                if (io->write_buffer_size == 0) {
356
                        /* Done */
357
0
                        io->write_buffer = NULL;
358
0
                        return TRUE;
359
0
                }
360
0
        }
361
362
0
        gssize ret = g_pollable_stream_write (io->ostream,
363
0
                                              io->write_buffer + io->written_bytes,
364
0
                                              io->write_buffer_size - io->written_bytes,
365
0
                                              blocking, cancellable, error);
366
0
        if (ret < 0)
367
0
                return FALSE;
368
369
0
        io->written_bytes += ret;
370
0
        return TRUE;
371
0
}
372
373
static gboolean
374
io_write_ready (GObject                  *stream,
375
                SoupClientMessageIOHTTP2 *io)
376
0
{
377
0
        GError *error = NULL;
378
379
0
        if (io->error) {
380
0
                g_clear_pointer (&io->write_source, g_source_unref);
381
0
                return G_SOURCE_REMOVE;
382
0
        }
383
384
0
        while (!error && nghttp2_session_want_write (io->session))
385
0
                io_write (io, FALSE, NULL, &error);
386
387
0
        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
388
0
                g_error_free (error);
389
0
                return G_SOURCE_CONTINUE;
390
0
        }
391
392
0
        if (error)
393
0
                set_io_error (io, error);
394
395
0
        g_clear_pointer (&io->write_source, g_source_unref);
396
0
        return G_SOURCE_REMOVE;
397
0
}
398
399
static gboolean io_write_idle_cb (SoupClientMessageIOHTTP2* io);
400
401
static void
402
io_try_write (SoupClientMessageIOHTTP2 *io,
403
              gboolean                  blocking)
404
0
{
405
0
        GError *error = NULL;
406
407
0
        if (io->write_source)
408
0
                return;
409
410
0
        if (io->in_callback) {
411
0
                if (blocking || !nghttp2_session_want_write (io->session))
412
0
                        return;
413
414
0
                if (io->write_idle_source)
415
0
                        return;
416
417
0
                io->write_idle_source = g_idle_source_new ();
418
0
                g_source_set_static_name (io->write_idle_source, "Soup HTTP/2 write idle source");
419
                /* Give write more priority than read */
420
0
                g_source_set_priority (io->write_idle_source, G_PRIORITY_DEFAULT - 1);
421
0
                g_source_set_callback (io->write_idle_source, (GSourceFunc)io_write_idle_cb, io, NULL);
422
0
                g_source_attach (io->write_idle_source, g_main_context_get_thread_default ());
423
0
                return;
424
0
        }
425
426
0
        if (io->write_idle_source) {
427
0
                g_source_destroy (io->write_idle_source);
428
0
                g_clear_pointer (&io->write_idle_source, g_source_unref);
429
0
        }
430
431
0
        while (!error && nghttp2_session_want_write (io->session))
432
0
                io_write (io, blocking, NULL, &error);
433
434
0
        if (!blocking && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
435
0
                g_clear_error (&error);
436
0
                io->write_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (io->ostream), NULL);
437
0
                g_source_set_static_name (io->write_source, "Soup HTTP/2 write source");
438
                /* Give write more priority than read */
439
0
                g_source_set_priority (io->write_source, G_PRIORITY_DEFAULT - 1);
440
0
                g_source_set_callback (io->write_source, (GSourceFunc)io_write_ready, io, NULL);
441
0
                g_source_attach (io->write_source, g_main_context_get_thread_default ());
442
0
                return;
443
0
        }
444
445
0
        if (error)
446
0
                set_io_error (io, error);
447
0
}
448
449
static gboolean
450
io_write_idle_cb (SoupClientMessageIOHTTP2* io)
451
0
{
452
0
        g_clear_pointer (&io->write_idle_source, g_source_unref);
453
0
        io_try_write (io, FALSE);
454
0
        return G_SOURCE_REMOVE;
455
0
}
456
457
static gboolean
458
io_read (SoupClientMessageIOHTTP2  *io,
459
         gboolean                   blocking,
460
         GCancellable              *cancellable,
461
         GError                   **error)
462
0
{
463
0
        guint8 buffer[16384];
464
0
        gssize read;
465
0
        int ret;
466
467
        /* Always try to write before read, in case there's a pending reset stream after an error. */
468
0
        io_try_write (io, blocking);
469
470
0
        if ((read = g_pollable_stream_read (io->istream, buffer, sizeof (buffer),
471
0
                                            blocking, cancellable, error)) < 0)
472
0
            return FALSE;
473
474
0
        if (read == 0) {
475
0
                g_set_error_literal (error, G_IO_ERROR,
476
0
                                     G_IO_ERROR_PARTIAL_INPUT,
477
0
                                     _("Connection terminated unexpectedly"));
478
0
                return FALSE;
479
0
        }
480
481
0
        g_warn_if_fail (io->in_callback == 0);
482
0
        ret = nghttp2_session_mem_recv (io->session, buffer, read);
483
0
        NGCHECK (ret);
484
0
        return ret > 0;
485
0
}
486
487
static gboolean
488
io_read_ready (GObject                  *stream,
489
               SoupClientMessageIOHTTP2 *io)
490
0
{
491
0
        GError *error = NULL;
492
0
        gboolean progress = TRUE;
493
0
        SoupConnection *conn;
494
495
0
        if (io->error) {
496
0
                g_clear_pointer (&io->read_source, g_source_unref);
497
0
                return G_SOURCE_REMOVE;
498
0
        }
499
500
        /* Mark the connection as in use to make sure it's not disconnected while
501
         * processing pending messages, for example if a goaway is received.
502
         */
503
0
        conn = g_weak_ref_get (&io->conn);
504
0
        if (conn)
505
0
                soup_connection_set_in_use (conn, TRUE);
506
507
0
        while (progress && nghttp2_session_want_read (io->session))
508
0
                progress = io_read (io, FALSE, NULL, &error);
509
510
0
        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
511
0
                g_error_free (error);
512
0
                if (conn) {
513
0
                        soup_connection_set_in_use (conn, FALSE);
514
0
                        g_object_unref (conn);
515
0
                }
516
0
                return G_SOURCE_CONTINUE;
517
0
        }
518
519
0
        io->is_shutdown = TRUE;
520
521
0
        if (error) {
522
0
                set_io_error (io, error);
523
0
                g_list_foreach (io->pending_io_messages,
524
0
                                (GFunc)soup_http2_message_data_check_status,
525
0
                                NULL);
526
0
        }
527
528
0
        g_clear_pointer (&io->read_source, g_source_unref);
529
0
        if (conn) {
530
0
                soup_connection_set_in_use (conn, FALSE);
531
0
                g_object_unref (conn);
532
0
        }
533
0
        return G_SOURCE_REMOVE;
534
0
}
535
536
static void
537
sniff_for_empty_response (SoupMessage *msg)
538
0
{
539
0
        if (soup_message_has_content_sniffer (msg)) {
540
0
                const char *content_type = soup_message_headers_get_content_type (soup_message_get_response_headers (msg), NULL);
541
0
                if (!content_type)
542
0
                     content_type = "text/plain";
543
0
                soup_message_content_sniffed (msg, content_type, NULL);
544
0
        }
545
0
}
546
547
static gboolean
548
message_has_content_length_zero (SoupMessage *msg)
549
0
{
550
0
        SoupMessageHeaders *headers = soup_message_get_response_headers (msg);
551
552
0
        if (soup_message_headers_get_encoding (headers) != SOUP_ENCODING_CONTENT_LENGTH)
553
0
                return FALSE;
554
555
0
        return soup_message_headers_get_content_length (headers) == 0;
556
0
}
557
558
static void
559
io_try_sniff_content (SoupHTTP2MessageData *data,
560
                      gboolean              blocking,
561
                      GCancellable         *cancellable)
562
0
{
563
0
        GError *error = NULL;
564
565
        /* This can re-enter in sync mode */
566
0
        if (data->in_io_try_sniff_content)
567
0
                return;
568
569
        /* Don't read the body while paused (e.g. waiting for a compression
570
         * dictionary to be resolved). Sniffing reads through the content
571
         * decoder, which must not be fed data before it is ready. The sniff is
572
         * retried from soup_client_message_io_http2_unpause(). */
573
0
        if (data->paused)
574
0
                return;
575
576
0
        if (message_has_content_length_zero (data->msg)) {
577
0
                sniff_for_empty_response (data->msg);
578
0
                h2_debug (data->io, data, "[DATA] Sniffed content (Content-Length was 0)");
579
0
                advance_state_from (data, STATE_READ_DATA_START, STATE_READ_DATA);
580
0
                if (data->item->async)
581
0
                        soup_http2_message_data_check_status_in_idle (data);
582
0
                return;
583
0
        }
584
585
0
        data->in_io_try_sniff_content = TRUE;
586
587
0
        if (soup_message_try_sniff_content (data->msg, data->decoded_data_istream, blocking, cancellable, &error)) {
588
0
                h2_debug (data->io, data, "[DATA] Sniffed content");
589
0
                advance_state_from (data, STATE_READ_DATA_START, STATE_READ_DATA);
590
0
                if (data->item->async)
591
0
                        soup_http2_message_data_check_status_in_idle (data);
592
0
        } else {
593
0
                h2_debug (data->io, data, "[DATA] Sniffer stream was not ready %s", error->message);
594
595
0
                g_clear_error (&error);
596
0
        }
597
598
0
        data->in_io_try_sniff_content = FALSE;
599
0
}
600
601
static void
602
soup_client_message_io_http2_terminate_session (SoupClientMessageIOHTTP2 *io)
603
0
{
604
0
        if (io->session_terminated)
605
0
                return;
606
607
0
        if (g_hash_table_size (io->messages) != 0)
608
0
                return;
609
610
0
        io->session_terminated = TRUE;
611
0
        NGCHECK (nghttp2_session_terminate_session (io->session, NGHTTP2_NO_ERROR));
612
0
        io_try_write (io, !io->async);
613
0
}
614
615
/* HTTP2 read callbacks */
616
617
static int
618
on_header_callback (nghttp2_session     *session,
619
                    const nghttp2_frame *frame,
620
                    const uint8_t       *name,
621
                    size_t               namelen,
622
                    const uint8_t       *value,
623
                    size_t               valuelen,
624
                    uint8_t              flags,
625
                    void                *user_data)
626
0
{
627
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
628
629
0
        if (!data)
630
0
                return 0;
631
632
0
        data->io->in_callback++;
633
634
0
        SoupMessage *msg = data->msg;
635
0
        if (name[0] == ':') {
636
0
                if (strcmp ((char *)name, ":status") == 0) {
637
0
                        guint status_code = (guint)g_ascii_strtoull ((char *)value, NULL, 10);
638
0
                        soup_message_set_status (msg, status_code, NULL);
639
0
                        data->io->in_callback--;
640
0
                        return 0;
641
0
                }
642
0
                g_debug ("Unknown header: %s = %s", name, value);
643
0
                data->io->in_callback--;
644
0
                return 0;
645
0
        }
646
647
0
        soup_message_headers_append_untrusted_data (soup_message_get_response_headers (data->msg),
648
0
                                                    (const char*)name, (const char*)value);
649
0
        data->io->in_callback--;
650
0
        return 0;
651
0
}
652
653
static int
654
on_invalid_header_callback (nghttp2_session     *session,
655
                            const nghttp2_frame *frame,
656
                            const uint8_t       *name,
657
                            size_t               namelen,
658
                            const uint8_t       *value,
659
                            size_t               valuelen,
660
                            uint8_t              flags,
661
                            void                *user_data)
662
0
{
663
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
664
665
0
        h2_debug (user_data, data, "[HEADERS] Invalid header received: name=[%.*s] value=[%.*s]", namelen, name, valuelen, value);
666
0
        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
667
0
}
668
669
static GError *
670
memory_stream_need_more_data_callback (SoupBodyInputStreamHttp2 *stream,
671
                                       GCancellable             *cancellable,
672
                                       gpointer                  user_data)
673
0
{
674
0
        SoupHTTP2MessageData *data = (SoupHTTP2MessageData*)user_data;
675
0
        GError *error = NULL;
676
677
0
        if (data->in_io_try_sniff_content)
678
0
                return NULL;
679
680
0
        if (nghttp2_session_want_read (data->io->session))
681
0
                io_read (data->io, TRUE, cancellable, &error);
682
683
0
        return error;
684
0
}
685
686
static void
687
memory_stream_read_data (SoupBodyInputStreamHttp2 *stream,
688
                         guint64                   bytes_read,
689
                         gpointer                  user_data)
690
0
{
691
0
        SoupHTTP2MessageData *data = (SoupHTTP2MessageData*)user_data;
692
693
0
        h2_debug (data->io, data, "[BODY_STREAM] Consumed %" G_GUINT64_FORMAT " bytes", bytes_read);
694
695
0
        NGCHECK (nghttp2_session_consume(data->io->session, data->stream_id, (size_t)bytes_read));
696
0
        io_try_write (data->io, !data->item->async);
697
0
}
698
699
static void
700
soup_http2_message_data_consume_buffered_body (SoupHTTP2MessageData *data)
701
0
{
702
0
        gsize buffered;
703
0
        gssize skipped;
704
0
        GError *error = NULL;
705
706
0
        if (!data->body_istream)
707
0
                return;
708
709
0
        buffered = soup_body_input_stream_http2_get_buffer_size (SOUP_BODY_INPUT_STREAM_HTTP2 (data->body_istream));
710
0
        if (!buffered)
711
0
                return;
712
713
0
        skipped = g_input_stream_skip (data->body_istream, buffered, NULL, &error);
714
0
        if (skipped < 0) {
715
0
                h2_debug (data->io, data, "[BODY_STREAM] Failed to consume %" G_GSIZE_FORMAT " buffered bytes before cancel: %s",
716
0
                          buffered, error->message);
717
0
                g_clear_error (&error);
718
0
                return;
719
0
        }
720
721
0
        h2_debug (data->io, data, "[BODY_STREAM] Consumed %zd/%" G_GSIZE_FORMAT " buffered bytes before cancel", skipped, buffered);
722
0
}
723
724
static int
725
on_begin_frame_callback (nghttp2_session        *session,
726
                         const nghttp2_frame_hd *hd,
727
                         void                   *user_data)
728
0
{
729
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, hd->stream_id);
730
731
0
        h2_debug (user_data, data, "[RECV] [%s] Beginning: stream_id=%u", soup_http2_frame_type_to_string (hd->type), hd->stream_id);
732
733
0
        if (!data)
734
0
                return 0;
735
736
0
        data->io->in_callback++;
737
738
0
        switch (hd->type) {
739
0
        case NGHTTP2_HEADERS:
740
0
                if (data->state == STATE_WRITE_DONE) {
741
0
                        soup_message_set_metrics_timestamp (data->item->msg, SOUP_MESSAGE_METRICS_RESPONSE_START);
742
0
                        advance_state_from (data, STATE_WRITE_DONE, STATE_READ_HEADERS);
743
0
                }
744
0
                break;
745
0
        case NGHTTP2_DATA:
746
0
                if (data->state < STATE_READ_DATA_START) {
747
0
                        g_assert (!data->body_istream);
748
0
                        data->body_istream = soup_body_input_stream_http2_new ();
749
0
                        g_signal_connect (data->body_istream, "need-more-data",
750
0
                                          G_CALLBACK (memory_stream_need_more_data_callback), data);
751
0
                        g_signal_connect (data->body_istream, "read-data",
752
0
                                          G_CALLBACK (memory_stream_read_data), data);
753
754
0
                        g_assert (!data->decoded_data_istream);
755
0
                        data->decoded_data_istream = soup_session_setup_message_body_input_stream (data->item->session,
756
0
                                                                                                   data->msg,
757
0
                                                                                                   data->body_istream,
758
0
                                                                                                   SOUP_STAGE_MESSAGE_BODY);
759
760
0
                        advance_state_from (data, STATE_READ_HEADERS, STATE_READ_DATA_START);
761
0
                }
762
0
                break;
763
0
        }
764
765
0
        data->io->in_callback--;
766
0
        return 0;
767
0
}
768
769
static void
770
handle_goaway (SoupClientMessageIOHTTP2 *io,
771
               guint32                   error_code,
772
               int32_t                   last_stream_id)
773
0
{
774
0
        GHashTableIter iter;
775
0
        SoupHTTP2MessageData *data;
776
777
0
        if (last_stream_id == G_MAXINT32)
778
0
                return;
779
780
0
        g_hash_table_iter_init (&iter, io->messages);
781
0
        while (g_hash_table_iter_next (&iter, NULL, (gpointer*)&data)) {
782
0
                if ((int32_t)data->stream_id > last_stream_id) {
783
                        /* RFC-9113 §6.8: stream was not processed by the server,
784
                         * it can be retried on a new connection regardless of error_code. */
785
0
                        data->can_be_restarted = TRUE;
786
0
                        set_http2_error_for_data (data, error_code);
787
0
                } else if (error_code != NGHTTP2_NO_ERROR) {
788
                        /* Stream might have been processed by the server but connection is dying with an error.
789
                         * Fail explicitly to avoid hanging indefinitely if the server is slow or fails
790
                         * to close the TCP connection (which it MUST do per §5.4.1).
791
                         * Per §6.8 only idempotent methods may be retried for processed streams. */
792
0
                        if (SOUP_METHOD_IS_IDEMPOTENT (soup_message_get_method (data->msg)))
793
0
                                data->can_be_restarted = TRUE;
794
0
                        set_http2_error_for_data (data, error_code);
795
0
                }
796
                 /* else: Graceful shutdown (NO_ERROR) and the server might have processed this stream.
797
                  * Per §6.8 it might still complete successfully, let it finish normally. */
798
0
        }
799
0
}
800
801
static int
802
on_frame_recv_callback (nghttp2_session     *session,
803
                        const nghttp2_frame *frame,
804
                        gpointer             user_data)
805
0
{
806
0
        SoupClientMessageIOHTTP2 *io = user_data;
807
0
        SoupHTTP2MessageData *data;
808
809
0
        io->in_callback++;
810
811
0
        if (frame->hd.stream_id == 0) {
812
0
                h2_debug (io, NULL, "[RECV] [%s] Received: stream_id=%u, flags=%u", soup_http2_frame_type_to_string (frame->hd.type), frame->hd.stream_id, frame->hd.flags);
813
814
0
                switch (frame->hd.type) {
815
0
                case NGHTTP2_GOAWAY:
816
0
                        h2_debug (io, NULL, "[RECV] GOAWAY: error=%s, last_stream_id=%d %s",
817
0
                                  nghttp2_http2_strerror (frame->goaway.error_code),
818
0
                                  frame->goaway.last_stream_id,
819
0
                                  frame->goaway.opaque_data ? (char *)frame->goaway.opaque_data : "");
820
0
                        handle_goaway (io, frame->goaway.error_code, frame->goaway.last_stream_id);
821
0
                        io->is_shutdown = TRUE;
822
0
                        soup_client_message_io_http2_terminate_session (io);
823
0
                        break;
824
0
                case NGHTTP2_WINDOW_UPDATE:
825
0
                        h2_debug (io, NULL, "[RECV] WINDOW_UPDATE: increment=%d, total=%d", frame->window_update.window_size_increment,
826
0
                                  nghttp2_session_get_remote_window_size (session));
827
0
                        break;
828
0
                }
829
830
0
                io->in_callback--;
831
0
                return 0;
832
0
        }
833
834
0
        data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
835
0
        h2_debug (io, data, "[RECV] [%s] Received: stream_id=%u, flags=%u", soup_http2_frame_type_to_string (frame->hd.type), frame->hd.stream_id, frame->hd.flags);
836
837
0
        if (!data) {
838
                /* This can happen in case of cancellation */
839
0
                io->in_callback--;
840
0
                return 0;
841
0
        }
842
843
0
        switch (frame->hd.type) {
844
0
        case NGHTTP2_HEADERS: {
845
0
                guint status = soup_message_get_status (data->msg);
846
847
0
                if (data->metrics)
848
0
                        data->metrics->response_header_bytes_received += frame->hd.length + FRAME_HEADER_SIZE;
849
850
0
                h2_debug (io, data, "[HEADERS] category=%s status=%u",
851
0
                          soup_http2_headers_category_to_string (frame->headers.cat), status);
852
0
                switch (frame->headers.cat) {
853
0
                case NGHTTP2_HCAT_HEADERS:
854
0
                        if (!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)) {
855
0
                                io->in_callback--;
856
0
                                return 0;
857
0
                        }
858
0
                        break;
859
0
                case NGHTTP2_HCAT_RESPONSE:
860
0
                        if (SOUP_STATUS_IS_INFORMATIONAL (status)) {
861
0
                                if (data->expect_continue && status == SOUP_STATUS_CONTINUE) {
862
0
                                        nghttp2_data_provider data_provider;
863
864
0
                                        data_provider.source.ptr = soup_message_get_request_body_stream (data->msg);
865
0
                                        data_provider.read_callback = on_data_source_read_callback;
866
0
                                        goffset content_length = soup_message_headers_get_content_length (soup_message_get_request_headers (data->msg));
867
0
                                        data->request_body_bytes_to_write = content_length > 0 ? content_length : -1;
868
0
                                        nghttp2_submit_data (io->session, NGHTTP2_FLAG_END_STREAM, frame->hd.stream_id, &data_provider);
869
0
                                        io_try_write (io, !data->item->async);
870
0
                                }
871
872
0
                                soup_message_got_informational (data->msg);
873
0
                                soup_message_cleanup_response (data->msg);
874
0
                                io->in_callback--;
875
0
                                return 0;
876
0
                        }
877
0
                        break;
878
0
                case NGHTTP2_HCAT_PUSH_RESPONSE:
879
0
                        g_warn_if_reached ();
880
0
                        break;
881
0
                default:
882
0
                        g_assert_not_reached ();
883
0
                }
884
885
0
                soup_message_got_headers (data->msg);
886
887
0
                if (soup_message_get_status (data->msg) == SOUP_STATUS_NO_CONTENT || frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
888
0
                        h2_debug (io, data, "Stream done");
889
0
                        advance_state_from (data, STATE_READ_HEADERS, STATE_READ_DATA_START);
890
0
                        sniff_for_empty_response (data->msg);
891
0
                        advance_state_from (data, STATE_READ_DATA_START, STATE_READ_DATA);
892
0
                        if (data->item->async)
893
0
                                soup_http2_message_data_check_status_in_idle (data);
894
0
                }
895
0
                break;
896
0
        }
897
0
        case NGHTTP2_DATA:
898
0
                h2_debug (io, data, "[RECV] [DATA] window=%d/%d", nghttp2_session_get_stream_effective_recv_data_length (session, frame->hd.stream_id),
899
0
                          nghttp2_session_get_stream_effective_local_window_size (session, frame->hd.stream_id));
900
0
                if (data->metrics)
901
0
                        data->metrics->response_body_bytes_received += frame->data.hd.length + FRAME_HEADER_SIZE;
902
0
                soup_message_got_body_data (data->msg, frame->data.hd.length + FRAME_HEADER_SIZE);
903
0
                if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
904
0
                        if (data->body_istream) {
905
0
                                soup_body_input_stream_http2_complete (SOUP_BODY_INPUT_STREAM_HTTP2 (data->body_istream));
906
0
                                if (data->state == STATE_READ_DATA_START)
907
0
                                        io_try_sniff_content (data, FALSE, data->item->cancellable);
908
0
                        }
909
0
                } else if (nghttp2_session_get_stream_effective_recv_data_length (session, frame->hd.stream_id) == 0) {
910
0
                        io_try_write (io, !data->item->async);
911
0
                }
912
0
                break;
913
0
        case NGHTTP2_RST_STREAM:
914
0
                if (frame->rst_stream.error_code != NGHTTP2_NO_ERROR)
915
0
                        set_http2_error_for_data (data, frame->rst_stream.error_code);
916
0
                break;
917
0
        case NGHTTP2_WINDOW_UPDATE:
918
0
                h2_debug (io, data, "[RECV] WINDOW_UPDATE: increment=%d, total=%d", frame->window_update.window_size_increment,
919
0
                          nghttp2_session_get_stream_remote_window_size (session, frame->hd.stream_id));
920
0
                if (nghttp2_session_get_stream_remote_window_size (session, frame->hd.stream_id) > 0)
921
0
                        io_try_write (io, !data->item->async);
922
0
                break;
923
0
        };
924
925
0
        io->in_callback--;
926
0
        return 0;
927
0
}
928
929
static int
930
on_data_chunk_recv_callback (nghttp2_session *session,
931
                             uint8_t          flags,
932
                             int32_t          stream_id,
933
                             const uint8_t   *data,
934
                             size_t           len,
935
                             void            *user_data)
936
0
{
937
0
        SoupClientMessageIOHTTP2 *io = user_data;
938
0
        SoupHTTP2MessageData *msgdata = nghttp2_session_get_stream_user_data (session, stream_id);
939
940
0
        h2_debug (io, msgdata, "[DATA] Received chunk, stream_id=%u len=%zu, flags=%u, paused=%d", stream_id, len, flags, msgdata ? msgdata->paused : 0);
941
942
0
        if (!msgdata) {
943
                /* This can happen in case of cancellation */
944
0
                return 0;
945
0
        }
946
947
0
        io->in_callback++;
948
949
0
        g_assert (msgdata->body_istream != NULL);
950
0
        soup_body_input_stream_http2_add_data (SOUP_BODY_INPUT_STREAM_HTTP2 (msgdata->body_istream), data, len);
951
0
        if (msgdata->state == STATE_READ_DATA_START)
952
0
                io_try_sniff_content (msgdata, FALSE, msgdata->item->cancellable);
953
954
0
        io->in_callback--;
955
0
        return 0;
956
0
}
957
958
/* HTTP2 write callbacks */
959
960
static int
961
on_before_frame_send_callback (nghttp2_session     *session,
962
                               const nghttp2_frame *frame,
963
                               void                *user_data)
964
0
{
965
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
966
967
0
        if (!data)
968
0
                return 0;
969
970
0
        data->io->in_callback++;
971
972
0
        switch (frame->hd.type) {
973
0
        case NGHTTP2_HEADERS:
974
0
                advance_state_from (data, STATE_NONE, STATE_WRITE_HEADERS);
975
0
                break;
976
0
        }
977
978
0
        data->io->in_callback--;
979
0
        return 0;
980
0
}
981
982
static gboolean
983
remove_closed_stream (SoupHTTP2MessageData *data,
984
                      gpointer              value,
985
                      nghttp2_frame        *frame)
986
0
{
987
0
        return data->stream_id == frame->hd.stream_id;
988
0
}
989
990
static gboolean
991
close_in_idle_cb (SoupClientMessageIOHTTP2 *io)
992
0
{
993
0
        g_task_return_boolean (io->close_task, TRUE);
994
0
        g_clear_object (&io->close_task);
995
996
0
        return G_SOURCE_REMOVE;
997
0
}
998
999
static int
1000
on_frame_send_callback (nghttp2_session     *session,
1001
                        const nghttp2_frame *frame,
1002
                        void                *user_data)
1003
0
{
1004
0
        SoupClientMessageIOHTTP2 *io = user_data;
1005
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
1006
1007
0
        io->in_callback++;
1008
1009
0
        switch (frame->hd.type) {
1010
0
        case NGHTTP2_HEADERS:
1011
0
                h2_debug (io, data, "[SEND] [HEADERS] stream_id=%u, category=%s finished=%d",
1012
0
                          frame->hd.stream_id, soup_http2_headers_category_to_string (frame->headers.cat),
1013
0
                          (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) ? 1 : 0);
1014
1015
0
                if (!data) {
1016
                        /* This can happen in case of cancellation */
1017
0
                        io->in_callback--;
1018
0
                        return 0;
1019
0
                }
1020
1021
0
                if (data->metrics)
1022
0
                        data->metrics->request_header_bytes_sent += frame->hd.length + FRAME_HEADER_SIZE;
1023
1024
0
                if (frame->hd.flags & NGHTTP2_FLAG_END_HEADERS) {
1025
0
                        soup_message_wrote_headers (data->msg);
1026
0
                        if (soup_message_get_request_body_stream (data->msg) == NULL) {
1027
0
                                advance_state_from (data, STATE_WRITE_HEADERS, STATE_WRITE_DONE);
1028
0
                                soup_message_wrote_body (data->msg);
1029
0
                        }
1030
0
                }
1031
0
                break;
1032
0
        case NGHTTP2_DATA:
1033
0
                if (!data) {
1034
                        /* This can happen in case of cancellation */
1035
0
                        io->in_callback--;
1036
0
                        return 0;
1037
0
                }
1038
1039
0
                if (data->state < STATE_WRITE_DATA)
1040
0
                        advance_state_from (data, STATE_WRITE_HEADERS, STATE_WRITE_DATA);
1041
1042
0
                h2_debug (io, data, "[SEND] [DATA] stream_id=%u, bytes=%zu, finished=%d",
1043
0
                          frame->hd.stream_id, frame->data.hd.length, frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
1044
0
                if (data->metrics) {
1045
0
                        data->metrics->request_body_bytes_sent += frame->hd.length + FRAME_HEADER_SIZE;
1046
0
                        data->metrics->request_body_size += frame->data.hd.length;
1047
0
                }
1048
0
                if (frame->data.hd.length)
1049
0
                        soup_message_wrote_body_data (data->msg, frame->data.hd.length);
1050
0
                if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
1051
0
                        advance_state_from (data, STATE_WRITE_DATA, STATE_WRITE_DONE);
1052
0
                        soup_message_wrote_body (data->msg);
1053
0
                }
1054
0
                break;
1055
0
        case NGHTTP2_RST_STREAM:
1056
0
                h2_debug (io, data, "[SEND] [RST_STREAM] stream_id=%u", frame->hd.stream_id);
1057
0
                if (g_hash_table_foreach_remove (io->closed_messages, (GHRFunc)remove_closed_stream, (gpointer)frame)) {
1058
0
                        SoupConnection *conn = g_weak_ref_get (&io->conn);
1059
1060
0
                        if (conn) {
1061
0
                                soup_connection_set_in_use (conn, FALSE);
1062
0
                                g_object_unref (conn);
1063
0
                        }
1064
0
                }
1065
1066
0
                break;
1067
0
        case NGHTTP2_GOAWAY:
1068
0
                h2_debug (io, data, "[SEND] [%s]", soup_http2_frame_type_to_string (frame->hd.type));
1069
0
                io->goaway_sent = TRUE;
1070
0
                if (io->close_task) {
1071
0
                        GSource *source;
1072
1073
                        /* Close in idle to ensure all pending io is finished first */
1074
0
                        source = g_idle_source_new ();
1075
0
                        g_source_set_static_name (source, "Soup HTTP/2 close source");
1076
0
                        g_source_set_callback (source, (GSourceFunc)close_in_idle_cb, io, NULL);
1077
0
                        g_source_attach (source, g_task_get_context (io->close_task));
1078
0
                        g_source_unref (source);
1079
0
                }
1080
0
                break;
1081
0
        case NGHTTP2_WINDOW_UPDATE:
1082
0
                h2_debug (io, data, "[SEND] [WINDOW_UPDATE] stream_id=%u increment=%d", frame->hd.stream_id, frame->window_update.window_size_increment);
1083
0
                break;
1084
0
        default:
1085
0
                h2_debug (io, data, "[SEND] [%s] stream_id=%u", soup_http2_frame_type_to_string (frame->hd.type), frame->hd.stream_id);
1086
0
                break;
1087
0
        }
1088
1089
0
        io->in_callback--;
1090
0
        return 0;
1091
0
}
1092
1093
static gboolean
1094
update_connection_in_use (gpointer        key,
1095
                          gpointer        value,
1096
                          SoupConnection *conn)
1097
0
{
1098
0
        soup_connection_set_in_use (conn, FALSE);
1099
1100
0
        return TRUE;
1101
0
}
1102
1103
static void
1104
process_pending_closed_messages (SoupClientMessageIOHTTP2 *io)
1105
0
{
1106
0
        SoupConnection *conn = g_weak_ref_get (&io->conn);
1107
1108
0
        if (!conn) {
1109
0
                g_hash_table_remove_all (io->closed_messages);
1110
0
                return;
1111
0
        }
1112
1113
0
        g_hash_table_foreach_remove (io->closed_messages, (GHRFunc)update_connection_in_use, conn);
1114
0
        g_object_unref (conn);
1115
0
}
1116
1117
static int
1118
on_frame_not_send_callback (nghttp2_session     *session,
1119
                            const nghttp2_frame *frame,
1120
                            int                  lib_error_code,
1121
                            void                *user_data)
1122
0
{
1123
0
        SoupClientMessageIOHTTP2 *io = user_data;
1124
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, frame->hd.stream_id);
1125
1126
0
        h2_debug (io, data, "[SEND] [%s] Failed stream %u: %s", soup_http2_frame_type_to_string (frame->hd.type),
1127
0
                  frame->hd.stream_id, nghttp2_strerror (lib_error_code));
1128
1129
0
        if (lib_error_code == NGHTTP2_ERR_SESSION_CLOSING)
1130
0
                process_pending_closed_messages (io);
1131
1132
0
        return 0;
1133
0
}
1134
1135
static int
1136
on_stream_close_callback (nghttp2_session *session,
1137
                          int32_t          stream_id,
1138
                          uint32_t         error_code,
1139
                          void            *user_data)
1140
0
{
1141
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, stream_id);
1142
1143
0
        h2_debug (user_data, data, "[SESSION] Closed stream %u: %s", stream_id, nghttp2_http2_strerror (error_code));
1144
0
        if (!data)
1145
0
                return 0;
1146
1147
0
        data->io->in_callback++;
1148
1149
0
        switch (error_code) {
1150
0
        case NGHTTP2_NO_ERROR:
1151
0
                break;
1152
0
        case NGHTTP2_REFUSED_STREAM:
1153
0
                if (data->state < STATE_READ_DATA_START)
1154
0
                        data->can_be_restarted = TRUE;
1155
0
                break;
1156
0
        case NGHTTP2_HTTP_1_1_REQUIRED:
1157
0
                soup_message_set_force_http_version (data->item->msg, SOUP_HTTP_1_1);
1158
0
                data->can_be_restarted = TRUE;
1159
0
                break;
1160
0
        default:
1161
0
                set_http2_error_for_data (data, error_code);
1162
0
                break;
1163
0
        }
1164
1165
0
        if (data->item->async)
1166
0
                soup_http2_message_data_check_status_in_idle (data);
1167
1168
0
        data->io->in_callback--;
1169
0
        return 0;
1170
0
}
1171
1172
static gboolean
1173
on_data_readable (GInputStream *stream,
1174
                  gpointer      user_data)
1175
0
{
1176
0
        SoupHTTP2MessageData *data = (SoupHTTP2MessageData*)user_data;
1177
1178
0
        h2_debug (data->io, data, "on data readable");
1179
1180
0
        NGCHECK (nghttp2_session_resume_data (data->io->session, data->stream_id));
1181
0
        io_try_write (data->io, !data->item->async);
1182
1183
0
        g_clear_pointer (&data->data_source_poll, g_source_unref);
1184
0
        return G_SOURCE_REMOVE;
1185
0
}
1186
1187
static void
1188
on_data_read (GInputStream *source,
1189
              GAsyncResult *res,
1190
              gpointer      user_data)
1191
0
{
1192
0
        SoupHTTP2MessageData *data = user_data;
1193
0
        GError *error = NULL;
1194
0
        gssize read = g_input_stream_read_finish (source, res, &error);
1195
1196
        /* This operation may have outlived the message data in which
1197
           case this will have been cancelled. */
1198
0
        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
1199
0
                g_error_free (error);
1200
0
                return;
1201
0
        }
1202
1203
0
        h2_debug (data->io, data, "[SEND_BODY] Read %zd", read);
1204
1205
0
        if (read < 0) {
1206
0
                g_byte_array_set_size (data->data_source_buffer, 0);
1207
0
                data->data_source_error = g_steal_pointer (&error);
1208
0
        } else if (read == 0) {
1209
0
                g_byte_array_set_size (data->data_source_buffer, 0);
1210
0
                data->data_source_eof = TRUE;
1211
0
        } else {
1212
0
                if (data->request_body_bytes_to_write > 0) {
1213
0
                        data->request_body_bytes_to_write -= read;
1214
0
                        if (data->request_body_bytes_to_write == 0)
1215
0
                                data->data_source_eof = TRUE;
1216
0
                }
1217
0
                g_byte_array_set_size (data->data_source_buffer, read);
1218
0
        }
1219
1220
0
        h2_debug (data->io, data, "[SEND_BODY] Resuming send");
1221
0
        NGCHECK (nghttp2_session_resume_data (data->io->session, data->stream_id));
1222
0
        io_try_write (data->io, !data->item->async);
1223
0
}
1224
1225
static void
1226
log_request_data (SoupHTTP2MessageData *data,
1227
                  const guint8         *buffer,
1228
                  gsize                 len)
1229
0
{
1230
0
        if (!data->logger)
1231
0
                return;
1232
1233
        /* NOTE: This doesn't exactly log data as it hits the network but
1234
           rather as soon as we read it from our source which is as good
1235
           as we can do since nghttp handles the actual io. */
1236
0
        soup_logger_log_request_data (data->logger, data->msg, (const char *)buffer, len);
1237
0
}
1238
1239
static void
1240
on_data_source_cancelled (GCancellable *cancellable,
1241
                          gpointer      data)
1242
0
{
1243
0
        GCancellable *linked_cancellable = G_CANCELLABLE (data);
1244
0
        g_cancellable_cancel (linked_cancellable);
1245
0
}
1246
1247
static ssize_t
1248
on_data_source_read_callback (nghttp2_session     *session,
1249
                              int32_t              stream_id,
1250
                              uint8_t             *buf,
1251
                              size_t               length,
1252
                              uint32_t            *data_flags,
1253
                              nghttp2_data_source *source,
1254
                              void                *user_data)
1255
0
{
1256
0
        SoupClientMessageIOHTTP2 *io = user_data;
1257
0
        SoupHTTP2MessageData *data = nghttp2_session_get_stream_user_data (session, stream_id);
1258
1259
0
        h2_debug (io, data, "[SEND_BODY] stream_id=%u, paused=%d", stream_id, data ? data->paused : 0);
1260
1261
0
        if (!data) {
1262
                /* This can happen in case of cancellation */
1263
0
                return 0;
1264
0
        }
1265
1266
0
        data->io->in_callback++;
1267
1268
0
        if (!data->item->async) {
1269
0
                gssize read;
1270
0
                GError *error = NULL;
1271
1272
0
                read = g_input_stream_read (source->ptr, buf, length, data->item->cancellable, &error);
1273
0
                if (read) {
1274
0
                        if (data->request_body_bytes_to_write > 0) {
1275
0
                                data->request_body_bytes_to_write -= read;
1276
0
                                if (data->request_body_bytes_to_write == 0)
1277
0
                                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1278
0
                        }
1279
0
                        h2_debug (data->io, data, "[SEND_BODY] Read %zd%s", read, *data_flags & NGHTTP2_DATA_FLAG_EOF ? ", EOF" : "");
1280
0
                        log_request_data (data, buf, read);
1281
0
                }
1282
1283
0
                if (read < 0) {
1284
0
                        set_error_for_data (data, g_steal_pointer (&error));
1285
0
                        data->io->in_callback--;
1286
0
                        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1287
0
                }
1288
1289
0
                if (read == 0) {
1290
0
                        h2_debug (data->io, data, "[SEND_BODY] EOF");
1291
0
                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1292
0
                }
1293
1294
0
                data->io->in_callback--;
1295
0
                return read;
1296
0
        }
1297
1298
        /* We support pollable streams in the best case because they
1299
         * should perform better with one fewer copy of each buffer and no threading. */
1300
0
        if (G_IS_POLLABLE_INPUT_STREAM (source->ptr) && g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (source->ptr))) {
1301
0
                GPollableInputStream *in_stream = G_POLLABLE_INPUT_STREAM (source->ptr);
1302
0
                GError *error = NULL;
1303
1304
0
                gssize read = g_pollable_input_stream_read_nonblocking  (in_stream, buf, length, data->item->cancellable, &error);
1305
1306
0
                if (read) {
1307
0
                        if (data->request_body_bytes_to_write > 0) {
1308
0
                                data->request_body_bytes_to_write -= read;
1309
0
                                if (data->request_body_bytes_to_write == 0)
1310
0
                                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1311
0
                        }
1312
0
                        h2_debug (data->io, data, "[SEND_BODY] Read %zd%s", read, *data_flags & NGHTTP2_DATA_FLAG_EOF ? ", EOF" : "");
1313
0
                        log_request_data (data, buf, read);
1314
0
                }
1315
1316
0
                if (read < 0) {
1317
0
                        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) {
1318
0
                                g_assert (data->data_source_poll == NULL);
1319
1320
0
                                h2_debug (data->io, data, "[SEND_BODY] Polling");
1321
0
                                data->data_source_poll = g_pollable_input_stream_create_source (in_stream, data->item->cancellable);
1322
0
                                g_source_set_static_name (data->data_source_poll, "Soup HTTP/2 data polling");
1323
0
                                g_source_set_callback (data->data_source_poll, (GSourceFunc)on_data_readable, data, NULL);
1324
0
                                g_source_set_priority (data->data_source_poll, get_data_io_priority (data));
1325
0
                                g_source_attach (data->data_source_poll, g_main_context_get_thread_default ());
1326
1327
0
                                g_error_free (error);
1328
0
                                data->io->in_callback--;
1329
0
                                return NGHTTP2_ERR_DEFERRED;
1330
0
                        }
1331
1332
0
                        set_error_for_data (data, g_steal_pointer (&error));
1333
0
                        data->io->in_callback--;
1334
0
                        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1335
0
                }
1336
0
                else if (read == 0) {
1337
0
                        h2_debug (data->io, data, "[SEND_BODY] EOF");
1338
0
                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1339
0
                }
1340
1341
0
                data->io->in_callback--;
1342
0
                return read;
1343
0
        } else {
1344
0
                GInputStream *in_stream = G_INPUT_STREAM (source->ptr);
1345
1346
                /* To support non-pollable input streams we always deffer reads
1347
                * and read async into a local buffer. The next time around we will
1348
                * send that buffer or error.
1349
                */
1350
0
                if (!data->data_source_buffer)
1351
0
                        data->data_source_buffer = g_byte_array_new ();
1352
1353
0
                guint buffer_len = data->data_source_buffer->len;
1354
0
                if (buffer_len) {
1355
0
                        if (data->data_source_eof) {
1356
0
                                h2_debug (data->io, data, "[SEND_BODY] Sending %zu, EOF", buffer_len);
1357
0
                                *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1358
0
                        } else
1359
0
                                h2_debug (data->io, data, "[SEND_BODY] Sending %zu", buffer_len);
1360
0
                        g_assert (buffer_len <= length); /* QUESTION: Maybe not reliable */
1361
0
                        memcpy (buf, data->data_source_buffer->data, buffer_len);
1362
0
                        log_request_data (data, buf, buffer_len);
1363
0
                        g_byte_array_set_size (data->data_source_buffer, 0);
1364
0
                        data->io->in_callback--;
1365
0
                        return buffer_len;
1366
0
                } else if (data->data_source_eof) {
1367
0
                        h2_debug (data->io, data, "[SEND_BODY] EOF");
1368
0
                        *data_flags |= NGHTTP2_DATA_FLAG_EOF;
1369
0
                        data->io->in_callback--;
1370
0
                        return 0;
1371
0
                } else if (data->data_source_error) {
1372
0
                        set_error_for_data (data, g_steal_pointer (&data->data_source_error));
1373
0
                        data->io->in_callback--;
1374
0
                        return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
1375
0
                } else {
1376
0
                        h2_debug (data->io, data, "[SEND_BODY] Reading async");
1377
0
                        g_byte_array_set_size (data->data_source_buffer, length);
1378
0
                        if (!data->data_source_cancellable) {
1379
0
                                data->data_source_cancellable = g_cancellable_new ();
1380
0
                                if (data->item->cancellable) {
1381
0
                                        data->data_source_message_cancellable = g_object_ref (data->item->cancellable);
1382
0
                                        data->data_source_cancellable_id =
1383
0
                                                g_cancellable_connect (data->data_source_message_cancellable, G_CALLBACK (on_data_source_cancelled),
1384
0
                                                                       g_object_ref (data->data_source_cancellable),  g_object_unref);
1385
0
                                }
1386
0
                        }
1387
0
                        g_input_stream_read_async (in_stream, data->data_source_buffer->data, length,
1388
0
                                                   get_data_io_priority (data),
1389
0
                                                   data->data_source_cancellable,
1390
0
                                                   (GAsyncReadyCallback)on_data_read, data);
1391
0
                        data->io->in_callback--;
1392
0
                        return NGHTTP2_ERR_DEFERRED;
1393
0
                }
1394
0
        }
1395
0
}
1396
1397
/* HTTP2 IO functions */
1398
1399
static int32_t
1400
message_priority_to_weight (SoupMessage *msg)
1401
0
{
1402
0
        switch (soup_message_get_priority (msg)) {
1403
0
        case SOUP_MESSAGE_PRIORITY_VERY_LOW:
1404
0
                return NGHTTP2_MIN_WEIGHT;
1405
0
        case SOUP_MESSAGE_PRIORITY_LOW:
1406
0
                return (NGHTTP2_DEFAULT_WEIGHT - NGHTTP2_MIN_WEIGHT) / 2;
1407
0
        case SOUP_MESSAGE_PRIORITY_NORMAL:
1408
0
                return NGHTTP2_DEFAULT_WEIGHT;
1409
0
        case SOUP_MESSAGE_PRIORITY_HIGH:
1410
0
                return (NGHTTP2_MAX_WEIGHT - NGHTTP2_DEFAULT_WEIGHT) / 2;
1411
0
        case SOUP_MESSAGE_PRIORITY_VERY_HIGH:
1412
0
                return NGHTTP2_MAX_WEIGHT;
1413
0
        }
1414
1415
0
        return NGHTTP2_DEFAULT_WEIGHT;
1416
0
}
1417
1418
static void
1419
message_priority_changed (SoupHTTP2MessageData *data)
1420
0
{
1421
0
        nghttp2_priority_spec priority_spec;
1422
0
        int32_t weight;
1423
1424
0
        if (!data->stream_id)
1425
0
                return;
1426
1427
0
        weight = message_priority_to_weight (data->msg);
1428
0
        h2_debug (data->io, data, "[PRIORITY] weight=%d", weight);
1429
1430
0
        nghttp2_priority_spec_init (&priority_spec, 0, weight, 0);
1431
0
        NGCHECK (nghttp2_submit_priority (data->io->session, NGHTTP2_FLAG_NONE, data->stream_id, &priority_spec));
1432
0
        io_try_write (data->io, !data->item->async);
1433
0
}
1434
1435
static SoupHTTP2MessageData *
1436
add_message_to_io_data (SoupClientMessageIOHTTP2  *io,
1437
                        SoupMessageQueueItem      *item,
1438
                        SoupMessageIOCompletionFn  completion_cb,
1439
                        gpointer                   completion_data)
1440
0
{
1441
0
        SoupHTTP2MessageData *data = g_new0 (SoupHTTP2MessageData, 1);
1442
1443
0
        data->item = soup_message_queue_item_ref (item);
1444
0
        data->msg = item->msg;
1445
0
        data->metrics = soup_message_get_metrics (data->msg);
1446
0
        data->request_body_bytes_to_write = -1;
1447
0
        data->completion_cb = completion_cb;
1448
0
        data->completion_data = completion_data;
1449
0
        data->stream_id = 0;
1450
0
        data->io = io;
1451
1452
0
        if (!g_hash_table_insert (io->messages, item->msg, data))
1453
0
                g_warn_if_reached ();
1454
1455
0
        g_signal_connect_swapped (data->msg, "notify::priority",
1456
0
                                  G_CALLBACK (message_priority_changed),
1457
0
                                  data);
1458
1459
0
        return data;
1460
0
}
1461
1462
static void
1463
soup_http2_message_data_close (SoupHTTP2MessageData *data)
1464
0
{
1465
        /* Message data in close state is just waiting for reset stream to be sent
1466
         * to be removed from the messages hash table. Everything is reset but
1467
         * stream_id and io.
1468
         */
1469
0
        if (data->body_istream) {
1470
0
                g_signal_handlers_disconnect_by_data (data->body_istream, data);
1471
0
                g_clear_object (&data->body_istream);
1472
0
        }
1473
1474
0
        if (data->data_source_cancellable_id) {
1475
0
                g_cancellable_disconnect (data->data_source_message_cancellable, data->data_source_cancellable_id);
1476
0
                data->data_source_cancellable_id = 0;
1477
0
                g_clear_object (&data->data_source_message_cancellable);
1478
0
        }
1479
0
        if (data->data_source_cancellable) {
1480
0
                g_cancellable_cancel(data->data_source_cancellable);
1481
0
                g_clear_object(&data->data_source_cancellable);
1482
0
        }
1483
1484
0
        if (data->msg)
1485
0
                g_signal_handlers_disconnect_by_data (data->msg, data);
1486
1487
0
        soup_http2_message_data_destroy_check_status_idle_source (data);
1488
1489
0
        data->msg = NULL;
1490
0
        data->metrics = NULL;
1491
0
        g_clear_pointer (&data->item, soup_message_queue_item_unref);
1492
0
        g_clear_object (&data->decoded_data_istream);
1493
1494
0
        if (data->data_source_poll) {
1495
0
                g_source_destroy (data->data_source_poll);
1496
0
                g_clear_pointer (&data->data_source_poll, g_source_unref);
1497
0
        }
1498
1499
0
        g_clear_error (&data->data_source_error);
1500
0
        g_clear_pointer (&data->data_source_buffer, g_byte_array_unref);
1501
1502
0
        g_clear_error (&data->error);
1503
1504
0
        data->completion_cb = NULL;
1505
0
        data->completion_data = NULL;
1506
0
}
1507
1508
static void
1509
soup_http2_message_data_free (SoupHTTP2MessageData *data)
1510
0
{
1511
0
        soup_http2_message_data_close (data);
1512
0
        g_free (data);
1513
0
}
1514
1515
static gboolean
1516
request_header_is_valid (const char *name)
1517
0
{
1518
0
        static GHashTable *invalid_request_headers = NULL;
1519
1520
0
        if (g_once_init_enter (&invalid_request_headers)) {
1521
0
                GHashTable *headers;
1522
1523
0
                headers= g_hash_table_new (soup_str_case_hash, soup_str_case_equal);
1524
0
                g_hash_table_add (headers, "Connection");
1525
0
                g_hash_table_add (headers, "Keep-Alive");
1526
0
                g_hash_table_add (headers, "Proxy-Connection");
1527
0
                g_hash_table_add (headers, "Transfer-Encoding");
1528
0
                g_hash_table_add (headers, "Upgrade");
1529
1530
0
                g_once_init_leave (&invalid_request_headers, headers);
1531
0
        }
1532
1533
0
        return !g_hash_table_contains (invalid_request_headers, name);
1534
0
}
1535
1536
static void
1537
send_message_request (SoupMessage          *msg,
1538
                      SoupClientMessageIOHTTP2   *io,
1539
                      SoupHTTP2MessageData *data)
1540
0
{
1541
0
        GArray *headers = g_array_new (FALSE, FALSE, sizeof (nghttp2_nv));
1542
1543
0
        GUri *uri = soup_message_get_uri (msg);
1544
0
        char *host = soup_uri_get_host_for_headers (uri);
1545
0
        char *authority = NULL;
1546
0
        if (!soup_uri_uses_default_port (uri))
1547
0
                authority = g_strdup_printf ("%s:%d", host, g_uri_get_port (uri));
1548
0
        const char *authority_header = authority ? authority : host;
1549
1550
0
        char *path_and_query;
1551
0
        if (soup_message_get_is_options_ping (msg))
1552
0
                path_and_query = g_strdup ("*");
1553
0
        else
1554
0
                path_and_query = g_strdup_printf ("%s%c%s", g_uri_get_path (uri), g_uri_get_query (uri) ? '?' : '\0', g_uri_get_query (uri));
1555
1556
0
        const nghttp2_nv pseudo_headers[] = {
1557
0
                MAKE_NV3 (":method", soup_message_get_method (msg), NGHTTP2_NV_FLAG_NO_COPY_VALUE),
1558
0
                MAKE_NV2 (":scheme", g_uri_get_scheme (uri)),
1559
0
                MAKE_NV2 (":authority", authority_header),
1560
0
                MAKE_NV2 (":path", path_and_query),
1561
0
        };
1562
1563
0
        for (guint i = 0; i < G_N_ELEMENTS (pseudo_headers); ++i) {
1564
0
                g_array_append_val (headers, pseudo_headers[i]);
1565
0
        }
1566
1567
0
        SoupMessageHeaders *request_headers = soup_message_get_request_headers (msg);
1568
0
        SoupMessageHeadersIter iter;
1569
0
        const char *name, *value;
1570
0
        soup_message_headers_iter_init (&iter, request_headers);
1571
0
        while (soup_message_headers_iter_next (&iter, &name, &value)) {
1572
0
                if (!request_header_is_valid (name))
1573
0
                        continue;
1574
1575
0
                const nghttp2_nv nv = MAKE_NV2 (name, value);
1576
0
                g_array_append_val (headers, nv);
1577
0
        }
1578
1579
0
        GInputStream *body_stream = soup_message_get_request_body_stream (msg);
1580
0
        SoupSessionFeature *logger = soup_session_get_feature_for_message (data->item->session, SOUP_TYPE_LOGGER, data->msg);
1581
0
        if (logger && body_stream)
1582
0
                data->logger = SOUP_LOGGER (logger);
1583
1584
0
        nghttp2_priority_spec priority_spec;
1585
0
        nghttp2_priority_spec_init (&priority_spec, 0, message_priority_to_weight (msg), 0);
1586
1587
0
        int32_t stream_id;
1588
0
        if (body_stream && soup_message_headers_get_expectations (request_headers) & SOUP_EXPECTATION_CONTINUE) {
1589
0
                data->expect_continue = TRUE;
1590
0
                stream_id = nghttp2_submit_headers (io->session, 0, -1, &priority_spec, (const nghttp2_nv *)headers->data, headers->len, data);
1591
0
        } else {
1592
0
                nghttp2_data_provider data_provider;
1593
0
                if (body_stream) {
1594
0
                        data_provider.source.ptr = body_stream;
1595
0
                        data_provider.read_callback = on_data_source_read_callback;
1596
0
                        goffset content_length = soup_message_headers_get_content_length (request_headers);
1597
0
                        data->request_body_bytes_to_write = content_length > 0 ? content_length : -1;
1598
0
                }
1599
0
                stream_id = nghttp2_submit_request (io->session, &priority_spec, (const nghttp2_nv *)headers->data, headers->len, body_stream ? &data_provider : NULL, data);
1600
0
        }
1601
0
        if (stream_id == NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE) {
1602
0
                set_error_for_data (data,
1603
0
                                    g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
1604
0
                                                         "HTTP/2 Error: stream ID not available"));
1605
0
                data->can_be_restarted = TRUE;
1606
0
        } else {
1607
0
                NGCHECK (stream_id);
1608
0
                data->stream_id = stream_id;
1609
0
                h2_debug (io, data, "[SESSION] Request made for %s%s", authority_header, path_and_query);
1610
0
                io_try_write (io, !data->item->async);
1611
0
        }
1612
0
        g_array_free (headers, TRUE);
1613
0
        g_free (authority);
1614
0
        g_free (host);
1615
0
        g_free (path_and_query);
1616
0
}
1617
1618
static void
1619
soup_client_message_io_http2_send_item (SoupClientMessageIO       *iface,
1620
                                        SoupMessageQueueItem      *item,
1621
                                        SoupMessageIOCompletionFn  completion_cb,
1622
                                        gpointer                   user_data)
1623
0
{
1624
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1625
0
        SoupHTTP2MessageData *data = add_message_to_io_data (io, item, completion_cb, user_data);
1626
1627
0
        send_message_request (item->msg, io, data);
1628
0
}
1629
1630
static SoupHTTP2MessageData *
1631
get_data_for_message (SoupClientMessageIOHTTP2 *io,
1632
                      SoupMessage              *msg)
1633
0
{
1634
0
        return g_hash_table_lookup (io->messages, msg);
1635
0
}
1636
1637
static void
1638
soup_client_message_io_http2_finished (SoupClientMessageIO *iface,
1639
                                       SoupMessage         *msg)
1640
0
{
1641
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1642
0
        SoupHTTP2MessageData *data;
1643
0
  SoupMessageIOCompletionFn completion_cb;
1644
0
  gpointer completion_data;
1645
0
        SoupMessageIOCompletion completion;
1646
0
        gboolean is_closed;
1647
0
        SoupConnection *conn;
1648
1649
0
        data = get_data_for_message (io, msg);
1650
1651
0
        soup_http2_message_data_destroy_check_status_idle_source (data);
1652
1653
0
        completion = data->state < STATE_READ_DONE ? SOUP_MESSAGE_IO_INTERRUPTED : SOUP_MESSAGE_IO_COMPLETE;
1654
1655
0
        h2_debug (io, data, "Finished stream %u: %s", data->stream_id, completion == SOUP_MESSAGE_IO_COMPLETE ? "completed" : "interrupted");
1656
1657
0
        if (completion == SOUP_MESSAGE_IO_INTERRUPTED)
1658
0
                soup_http2_message_data_consume_buffered_body (data);
1659
1660
0
  completion_cb = data->completion_cb;
1661
0
  completion_data = data->completion_data;
1662
1663
0
  g_object_ref (msg);
1664
1665
0
        is_closed = nghttp2_session_get_stream_user_data (io->session, data->stream_id) == NULL;
1666
0
        nghttp2_session_set_stream_user_data (io->session, data->stream_id, NULL);
1667
1668
0
        conn = g_weak_ref_get (&io->conn);
1669
1670
0
        if (!io->is_shutdown && !is_closed) {
1671
0
                NGCHECK (nghttp2_submit_rst_stream (io->session, NGHTTP2_FLAG_NONE, data->stream_id,
1672
0
                                                    completion == SOUP_MESSAGE_IO_COMPLETE ? NGHTTP2_NO_ERROR : NGHTTP2_CANCEL));
1673
0
                soup_http2_message_data_close (data);
1674
1675
0
                if (!g_hash_table_steal (io->messages, msg))
1676
0
                        g_warn_if_reached ();
1677
0
                if (!g_hash_table_add (io->closed_messages, data))
1678
0
                        g_warn_if_reached ();
1679
1680
0
                if (conn)
1681
0
                        soup_connection_set_in_use (conn, TRUE);
1682
1683
0
                io_try_write (io, !io->async);
1684
0
        } else {
1685
0
                if (!g_hash_table_remove (io->messages, msg))
1686
0
                        g_warn_if_reached ();
1687
0
        }
1688
1689
0
  if (completion_cb)
1690
0
    completion_cb (G_OBJECT (msg), SOUP_MESSAGE_IO_COMPLETE, completion_data);
1691
1692
0
  g_object_unref (msg);
1693
1694
0
        if (io->is_shutdown)
1695
0
                soup_client_message_io_http2_terminate_session (io);
1696
1697
0
        g_clear_object (&conn);
1698
0
}
1699
1700
static void
1701
soup_client_message_io_http2_pause (SoupClientMessageIO *iface,
1702
                                    SoupMessage         *msg)
1703
0
{
1704
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1705
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1706
1707
0
        h2_debug (io, data, "[SESSION] Paused");
1708
1709
0
        if (data->paused)
1710
0
                g_warn_if_reached ();
1711
1712
0
        data->paused = TRUE;
1713
0
}
1714
1715
static void
1716
soup_client_message_io_http2_unpause (SoupClientMessageIO *iface,
1717
                                      SoupMessage         *msg)
1718
0
{
1719
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1720
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1721
1722
0
        h2_debug (io, data, "[SESSION] Unpaused");
1723
1724
0
        if (!data->paused)
1725
0
                g_warn_if_reached ();
1726
1727
0
        data->paused = FALSE;
1728
1729
        /* Body data that arrived while paused was buffered but not sniffed (see
1730
         * io_try_sniff_content()), and won't re-trigger on_data_chunk_recv_callback().
1731
         * Retry sniffing now that the content decoder is ready. */
1732
0
        if (data->state == STATE_READ_DATA_START && data->decoded_data_istream)
1733
0
                io_try_sniff_content (data, FALSE, data->item->cancellable);
1734
1735
0
        if (data->item->async)
1736
0
                soup_http2_message_data_check_status (data);
1737
0
}
1738
1739
static void
1740
soup_client_message_io_http2_stolen (SoupClientMessageIO *iface)
1741
0
{
1742
0
        g_assert_not_reached ();
1743
0
}
1744
1745
static gboolean
1746
soup_client_message_io_http2_in_progress (SoupClientMessageIO *iface,
1747
                                          SoupMessage         *msg)
1748
0
{
1749
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1750
1751
0
        return io && get_data_for_message (io, msg) != NULL;
1752
0
}
1753
1754
static gboolean
1755
soup_client_message_io_http2_is_paused (SoupClientMessageIO *iface,
1756
                                        SoupMessage         *msg)
1757
0
{
1758
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1759
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1760
1761
0
        return data->paused;
1762
0
}
1763
1764
static gboolean
1765
soup_client_message_io_http2_is_open (SoupClientMessageIO *iface)
1766
0
{
1767
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1768
1769
0
        if (!nghttp2_session_check_request_allowed (io->session))
1770
0
                return FALSE;
1771
1772
0
        return !io->is_shutdown && !io->error;
1773
0
}
1774
1775
static gboolean
1776
soup_client_message_io_http2_is_reusable (SoupClientMessageIO *iface)
1777
0
{
1778
0
        return soup_client_message_io_http2_is_open (iface);
1779
0
}
1780
1781
static GCancellable *
1782
soup_client_message_io_http2_get_cancellable (SoupClientMessageIO *iface,
1783
                                              SoupMessage         *msg)
1784
0
{
1785
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1786
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1787
1788
0
        return data ? data->item->cancellable : NULL;
1789
0
}
1790
1791
static void
1792
client_stream_eof (SoupClientInputStream *stream,
1793
                   gpointer               user_data)
1794
0
{
1795
0
  SoupMessage *msg = user_data;
1796
0
  SoupClientMessageIOHTTP2 *io = get_io_data (msg);
1797
1798
0
        if (!io) {
1799
0
                g_warn_if_reached ();
1800
0
                return;
1801
0
        }
1802
1803
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1804
0
        h2_debug (io, data, "Client stream EOF");
1805
0
        soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_END);
1806
0
        advance_state_from (data, STATE_READ_DATA, STATE_READ_DONE);
1807
0
        io->ever_used = TRUE;
1808
0
        g_signal_handlers_disconnect_by_func (stream, client_stream_eof, msg);
1809
0
        soup_message_got_body (data->msg);
1810
0
}
1811
1812
static GInputStream *
1813
soup_client_message_io_http2_get_response_istream (SoupClientMessageIO  *iface,
1814
                                                   SoupMessage          *msg,
1815
                                                   GError              **error)
1816
0
{
1817
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1818
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1819
0
        GInputStream *client_stream, *base_stream;
1820
1821
0
        if (data->decoded_data_istream)
1822
0
                base_stream = g_object_ref (data->decoded_data_istream);
1823
0
        else /* For example with status_code == SOUP_STATUS_NO_CONTENT */
1824
0
                base_stream = g_memory_input_stream_new ();
1825
1826
0
        client_stream = soup_client_input_stream_new (base_stream, msg);
1827
0
        g_signal_connect (client_stream, "eof", G_CALLBACK (client_stream_eof), msg);
1828
1829
0
        g_object_unref (base_stream);
1830
1831
0
        return client_stream;
1832
0
}
1833
1834
static gboolean
1835
io_run (SoupHTTP2MessageData *data,
1836
        GCancellable         *cancellable,
1837
        GError              **error)
1838
0
{
1839
0
        SoupClientMessageIOHTTP2 *io = data->io;
1840
0
        gboolean progress = FALSE;
1841
1842
0
        if (data->state < STATE_WRITE_DONE && !io->in_callback && nghttp2_session_want_write (io->session))
1843
0
                progress = io_write (io, TRUE, cancellable, error);
1844
0
        else if (data->state < STATE_READ_DONE && !io->in_callback && nghttp2_session_want_read (io->session))
1845
0
                progress = io_read (io, TRUE, cancellable, error);
1846
1847
0
        return progress;
1848
0
}
1849
1850
static gboolean
1851
io_run_until (SoupClientMessageIOHTTP2 *io,
1852
              SoupMessage              *msg,
1853
              SoupHTTP2IOState          state,
1854
              GCancellable             *cancellable,
1855
              GError                  **error)
1856
0
{
1857
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1858
0
  gboolean progress = TRUE, done;
1859
0
  GError *my_error = NULL;
1860
1861
0
  if (g_cancellable_set_error_if_cancelled (cancellable, error))
1862
0
    return FALSE;
1863
0
  else if (!io) {
1864
0
    g_set_error_literal (error, G_IO_ERROR,
1865
0
             G_IO_ERROR_CANCELLED,
1866
0
             _("Operation was cancelled"));
1867
0
    return FALSE;
1868
0
  }
1869
1870
0
  g_object_ref (msg);
1871
1872
0
  while (progress && get_io_data (msg) == io && !data->paused && !data->error && data->state < state)
1873
0
                progress = io_run (data, cancellable, &my_error);
1874
1875
0
        if (my_error) {
1876
0
                io->is_shutdown = TRUE;
1877
0
                set_io_error (io, my_error);
1878
0
        }
1879
1880
0
        if (io->error && !data->error)
1881
0
                data->error = g_error_copy (io->error);
1882
1883
0
  if (data->error) {
1884
0
                g_propagate_error (error, g_steal_pointer (&data->error));
1885
0
    g_object_unref (msg);
1886
0
    return FALSE;
1887
0
        }
1888
1889
0
        if (get_io_data (msg) != io) {
1890
0
    g_set_error_literal (error, G_IO_ERROR,
1891
0
             G_IO_ERROR_CANCELLED,
1892
0
             _("Operation was cancelled"));
1893
0
    g_object_unref (msg);
1894
0
    return FALSE;
1895
0
  }
1896
1897
0
  done = data->state >= state;
1898
1899
0
  g_object_unref (msg);
1900
0
  return done;
1901
0
}
1902
1903
static gboolean
1904
soup_client_message_io_http2_run_until_read (SoupClientMessageIO  *iface,
1905
                                             SoupMessage          *msg,
1906
                                             GCancellable         *cancellable,
1907
                                             GError              **error)
1908
0
{
1909
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1910
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1911
0
        GError *my_error = NULL;
1912
1913
0
        if (io_run_until (io, msg, STATE_READ_DATA, cancellable, &my_error))
1914
0
                return TRUE;
1915
1916
0
        if (get_io_data (msg) == io) {
1917
0
                if (soup_http2_message_data_can_be_restarted (data, my_error))
1918
0
                        data->item->state = SOUP_MESSAGE_RESTARTING;
1919
0
                else
1920
0
                        soup_message_set_metrics_timestamp (msg, SOUP_MESSAGE_METRICS_RESPONSE_END);
1921
1922
0
                soup_client_message_io_http2_finished (iface, msg);
1923
0
        }
1924
1925
0
        g_propagate_error (error, my_error);
1926
1927
0
        return FALSE;
1928
0
}
1929
1930
static gboolean
1931
soup_client_message_io_http2_skip (SoupClientMessageIO *iface,
1932
                                   SoupMessage         *msg,
1933
                                   gboolean             blocking,
1934
                                   GCancellable        *cancellable,
1935
                                   GError             **error)
1936
0
{
1937
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1938
0
        SoupHTTP2MessageData *data;
1939
1940
0
        if (g_cancellable_set_error_if_cancelled (cancellable, error))
1941
0
                return FALSE;
1942
1943
0
        data = get_data_for_message (io, msg);
1944
0
        if (!data || data->state == STATE_READ_DONE)
1945
0
                return TRUE;
1946
1947
0
        h2_debug (io, data, "Skip");
1948
0
        soup_http2_message_data_consume_buffered_body (data);
1949
0
        NGCHECK (nghttp2_submit_rst_stream (io->session, NGHTTP2_FLAG_NONE, data->stream_id, NGHTTP2_CANCEL));
1950
0
        io_try_write (io, blocking);
1951
0
        return TRUE;
1952
0
}
1953
1954
static void
1955
soup_client_message_io_http2_run (SoupClientMessageIO *iface,
1956
                                  SoupMessage         *msg,
1957
                      gboolean             blocking)
1958
0
{
1959
0
        g_assert_not_reached ();
1960
0
}
1961
1962
static void
1963
soup_client_message_io_http2_run_until_read_async (SoupClientMessageIO *iface,
1964
                                                   SoupMessage         *msg,
1965
                                                   int                  io_priority,
1966
                                                   GCancellable        *cancellable,
1967
                                                   GAsyncReadyCallback  callback,
1968
                                                   gpointer             user_data)
1969
0
{
1970
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
1971
0
        SoupHTTP2MessageData *data = get_data_for_message (io, msg);
1972
1973
0
        data->task = g_task_new (msg, cancellable, callback, user_data);
1974
0
        g_task_set_source_tag (data->task, soup_client_message_io_http2_run_until_read_async);
1975
0
        g_task_set_priority (data->task, io_priority);
1976
0
        io->pending_io_messages = g_list_prepend (io->pending_io_messages, data);
1977
0
        if (data->error)
1978
0
                soup_http2_message_data_check_status (data);
1979
0
}
1980
1981
static void
1982
soup_client_message_io_http2_set_owner (SoupClientMessageIOHTTP2 *io,
1983
                                        GThread                  *owner)
1984
0
{
1985
0
        if (owner == io->owner)
1986
0
                return;
1987
1988
0
        io->owner = owner;
1989
0
        g_assert (!io->write_source);
1990
0
        g_assert (!io->write_idle_source);
1991
0
        if (io->read_source) {
1992
0
                g_source_destroy (io->read_source);
1993
0
                g_clear_pointer (&io->read_source, g_source_unref);
1994
0
        }
1995
1996
0
        io->async = g_main_context_is_owner (g_main_context_get_thread_default ());
1997
0
        if (!io->async)
1998
0
                return;
1999
2000
0
        io->read_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (io->istream), NULL);
2001
0
        g_source_set_static_name (io->read_source, "Soup HTTP/2 read source");
2002
0
        g_source_set_priority (io->read_source, G_PRIORITY_DEFAULT);
2003
0
        g_source_set_callback (io->read_source, (GSourceFunc)io_read_ready, io, NULL);
2004
0
        g_source_attach (io->read_source, g_main_context_get_thread_default ());
2005
0
}
2006
2007
static gboolean
2008
soup_client_message_io_http2_close_async (SoupClientMessageIO *iface,
2009
                                          SoupConnection      *conn,
2010
                                          GAsyncReadyCallback  callback)
2011
0
{
2012
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
2013
2014
0
        if (io->goaway_sent)
2015
0
                return FALSE;
2016
2017
0
        soup_client_message_io_http2_set_owner (io, g_thread_self ());
2018
0
        if (io->async) {
2019
0
                g_assert (!io->close_task);
2020
0
                io->close_task = g_task_new (conn, NULL, callback, NULL);
2021
0
                g_task_set_source_tag (io->close_task, soup_client_message_io_http2_close_async);
2022
0
        }
2023
2024
0
        soup_client_message_io_http2_terminate_session (io);
2025
0
        if (!io->async) {
2026
0
                g_assert (io->goaway_sent || io->error);
2027
0
                return FALSE;
2028
0
        }
2029
2030
0
        return TRUE;
2031
0
}
2032
2033
static void
2034
soup_client_message_io_http2_destroy (SoupClientMessageIO *iface)
2035
0
{
2036
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
2037
2038
0
        if (io->read_source) {
2039
0
                g_source_destroy (io->read_source);
2040
0
                g_source_unref (io->read_source);
2041
0
        }
2042
0
        if (io->write_source) {
2043
0
                g_source_destroy (io->write_source);
2044
0
                g_source_unref (io->write_source);
2045
0
        }
2046
0
        if (io->write_idle_source) {
2047
0
                g_source_destroy (io->write_idle_source);
2048
0
                g_source_unref (io->write_idle_source);
2049
0
        }
2050
2051
0
        g_weak_ref_clear (&io->conn);
2052
0
        g_clear_object (&io->stream);
2053
0
        g_clear_object (&io->close_task);
2054
0
        g_clear_pointer (&io->session, nghttp2_session_del);
2055
0
        g_clear_pointer (&io->messages, g_hash_table_unref);
2056
0
        g_clear_pointer (&io->closed_messages, g_hash_table_unref);
2057
0
        g_clear_pointer (&io->pending_io_messages, g_list_free);
2058
0
        g_clear_error (&io->error);
2059
2060
0
        g_free (io);
2061
0
}
2062
2063
static void
2064
soup_client_message_io_http2_owner_changed (SoupClientMessageIO *iface)
2065
0
{
2066
0
        SoupClientMessageIOHTTP2 *io = (SoupClientMessageIOHTTP2 *)iface;
2067
2068
0
        soup_client_message_io_http2_set_owner (io, g_thread_self ());
2069
0
}
2070
2071
static const SoupClientMessageIOFuncs io_funcs = {
2072
        soup_client_message_io_http2_destroy,
2073
        soup_client_message_io_http2_finished,
2074
        soup_client_message_io_http2_stolen,
2075
        soup_client_message_io_http2_send_item,
2076
        soup_client_message_io_http2_get_response_istream,
2077
        soup_client_message_io_http2_pause,
2078
        soup_client_message_io_http2_unpause,
2079
        soup_client_message_io_http2_is_paused,
2080
        soup_client_message_io_http2_run,
2081
        soup_client_message_io_http2_run_until_read,
2082
        soup_client_message_io_http2_run_until_read_async,
2083
        soup_client_message_io_http2_close_async,
2084
        soup_client_message_io_http2_skip,
2085
        soup_client_message_io_http2_is_open,
2086
        soup_client_message_io_http2_in_progress,
2087
        soup_client_message_io_http2_is_reusable,
2088
        soup_client_message_io_http2_get_cancellable,
2089
        soup_client_message_io_http2_owner_changed
2090
};
2091
2092
static void
2093
soup_client_message_io_http2_init (SoupClientMessageIOHTTP2 *io)
2094
0
{
2095
0
        soup_http2_debug_init ();
2096
2097
0
        nghttp2_session_callbacks *callbacks;
2098
0
        NGCHECK (nghttp2_session_callbacks_new (&callbacks));
2099
0
        nghttp2_session_callbacks_set_on_header_callback (callbacks, on_header_callback);
2100
0
        nghttp2_session_callbacks_set_on_invalid_header_callback (callbacks, on_invalid_header_callback);
2101
0
        nghttp2_session_callbacks_set_on_frame_recv_callback (callbacks, on_frame_recv_callback);
2102
0
        nghttp2_session_callbacks_set_on_data_chunk_recv_callback (callbacks, on_data_chunk_recv_callback);
2103
0
        nghttp2_session_callbacks_set_on_begin_frame_callback (callbacks, on_begin_frame_callback);
2104
0
        nghttp2_session_callbacks_set_before_frame_send_callback (callbacks, on_before_frame_send_callback);
2105
0
        nghttp2_session_callbacks_set_on_frame_not_send_callback (callbacks, on_frame_not_send_callback);
2106
0
        nghttp2_session_callbacks_set_on_frame_send_callback (callbacks, on_frame_send_callback);
2107
0
        nghttp2_session_callbacks_set_on_stream_close_callback (callbacks, on_stream_close_callback);
2108
2109
0
        nghttp2_option *option;
2110
2111
0
        nghttp2_option_new (&option);
2112
2113
0
#ifdef HAVE_NGHTTP2_OPTION_SET_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION
2114
0
        nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation (option, 1);
2115
0
#endif
2116
0
        nghttp2_option_set_no_auto_window_update (option, 1);
2117
2118
0
        NGCHECK (nghttp2_session_client_new2 (&io->session, callbacks, io, option));
2119
2120
0
        nghttp2_option_del (option);
2121
0
        nghttp2_session_callbacks_del (callbacks);
2122
2123
0
        io->messages = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)soup_http2_message_data_free);
2124
0
        io->closed_messages = g_hash_table_new_full (g_direct_hash, g_direct_equal, (GDestroyNotify)soup_http2_message_data_free, NULL);
2125
2126
0
        io->iface.funcs = &io_funcs;
2127
0
}
2128
2129
0
#define MAX_HEADER_TABLE_SIZE 65536 /* Match size used by Chromium/Firefox */
2130
2131
SoupClientMessageIO *
2132
soup_client_message_io_http2_new (SoupConnection *conn)
2133
0
{
2134
0
        SoupClientMessageIOHTTP2 *io = g_new0 (SoupClientMessageIOHTTP2, 1);
2135
0
        soup_client_message_io_http2_init (io);
2136
2137
0
        g_weak_ref_init (&io->conn, conn);
2138
2139
0
        io->stream = g_object_ref (soup_connection_get_iostream (conn));
2140
0
        io->istream = g_io_stream_get_input_stream (io->stream);
2141
0
        io->ostream = g_io_stream_get_output_stream (io->stream);
2142
0
        io->connection_id = soup_connection_get_id (conn);
2143
2144
0
        soup_client_message_io_http2_set_owner (io, soup_connection_get_owner (conn));
2145
2146
0
        int stream_window_size = soup_connection_get_http2_initial_stream_window_size (conn);
2147
0
        const nghttp2_settings_entry settings[] = {
2148
0
                { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, stream_window_size },
2149
0
                { NGHTTP2_SETTINGS_HEADER_TABLE_SIZE, MAX_HEADER_TABLE_SIZE },
2150
0
                { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 },
2151
0
        };
2152
0
        NGCHECK (nghttp2_submit_settings (io->session, NGHTTP2_FLAG_NONE, settings, G_N_ELEMENTS (settings)));
2153
0
        NGCHECK (nghttp2_session_set_local_window_size (io->session, NGHTTP2_FLAG_NONE, 0, soup_connection_get_http2_initial_window_size (conn)));
2154
0
        io_try_write (io, !io->async);
2155
2156
0
        return (SoupClientMessageIO *)io;
2157
0
}