Coverage Report

Created: 2026-09-01 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libsoup/libsoup/http1/soup-body-input-stream.c
Line
Count
Source
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2
/*
3
 * soup-body-input-stream.c
4
 *
5
 * Copyright 2012 Red Hat, Inc.
6
 */
7
8
#ifdef HAVE_CONFIG_H
9
#include <config.h>
10
#endif
11
12
#include <stdlib.h>
13
14
#include <glib/gi18n-lib.h>
15
16
#include "soup-body-input-stream.h"
17
#include "soup.h"
18
#include "soup-filter-input-stream.h"
19
20
typedef enum {
21
  SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE,
22
  SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END,
23
  SOUP_BODY_INPUT_STREAM_STATE_CHUNK,
24
  SOUP_BODY_INPUT_STREAM_STATE_TRAILERS,
25
  SOUP_BODY_INPUT_STREAM_STATE_DONE
26
} SoupBodyInputStreamState;
27
28
struct _SoupBodyInputStream {
29
  GFilterInputStream parent_instance;
30
};
31
32
typedef struct {
33
  GInputStream *base_stream;
34
35
  SoupEncoding  encoding;
36
  goffset       read_length;
37
  SoupBodyInputStreamState chunked_state;
38
  gboolean      eof;
39
40
  goffset       pos;
41
} SoupBodyInputStreamPrivate;
42
43
enum {
44
  CLOSED,
45
  LAST_SIGNAL
46
};
47
48
static guint signals[LAST_SIGNAL] = { 0 };
49
50
enum {
51
  PROP_0,
52
53
  PROP_ENCODING,
54
  PROP_CONTENT_LENGTH,
55
56
        LAST_PROPERTY
57
};
58
59
static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
60
61
static void soup_body_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data);
62
static void soup_body_input_stream_seekable_init (GSeekableIface *seekable_interface);
63
64
0
G_DEFINE_FINAL_TYPE_WITH_CODE (SoupBodyInputStream, soup_body_input_stream, G_TYPE_FILTER_INPUT_STREAM,
65
0
                               G_ADD_PRIVATE (SoupBodyInputStream)
66
0
             G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM,
67
0
                  soup_body_input_stream_pollable_init)
68
0
             G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE,
69
0
                  soup_body_input_stream_seekable_init))
70
0
71
0
static void
72
0
soup_body_input_stream_init (SoupBodyInputStream *bistream)
73
0
{
74
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
75
0
  priv->encoding = SOUP_ENCODING_NONE;
76
0
}
77
78
static void
79
soup_body_input_stream_constructed (GObject *object)
80
0
{
81
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
82
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
83
84
0
  G_OBJECT_CLASS (soup_body_input_stream_parent_class)->constructed (object);
85
86
0
  priv->base_stream = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (bistream));
87
88
0
  if (priv->encoding == SOUP_ENCODING_NONE ||
89
0
      (priv->encoding == SOUP_ENCODING_CONTENT_LENGTH &&
90
0
       priv->read_length == 0))
91
0
    priv->eof = TRUE;
92
0
}
93
94
static void
95
soup_body_input_stream_set_property (GObject *object, guint prop_id,
96
             const GValue *value, GParamSpec *pspec)
97
0
{
98
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
99
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
100
101
0
  switch (prop_id) {
102
0
  case PROP_ENCODING:
103
0
    priv->encoding = g_value_get_enum (value);
104
0
    if (priv->encoding == SOUP_ENCODING_CHUNKED)
105
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE;
106
0
    break;
107
0
  case PROP_CONTENT_LENGTH:
108
0
    priv->read_length = g_value_get_int64 (value);
109
0
    break;
110
0
  default:
111
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
112
0
    break;
113
0
  }
114
0
}
115
116
static void
117
soup_body_input_stream_get_property (GObject *object, guint prop_id,
118
             GValue *value, GParamSpec *pspec)
119
0
{
120
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
121
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
122
123
124
0
  switch (prop_id) {
125
0
  case PROP_ENCODING:
126
0
    g_value_set_enum (value, priv->encoding);
127
0
    break;
128
0
  case PROP_CONTENT_LENGTH:
129
0
    g_assert_not_reached ();
130
0
    break;
131
0
  default:
132
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
133
0
    break;
134
0
  }
135
0
}
136
137
static gssize
138
soup_body_input_stream_read_raw (SoupBodyInputStream  *bistream,
139
         void                 *buffer,
140
         gsize                 count,
141
         gboolean              blocking,
142
         GCancellable         *cancellable,
143
         GError              **error)
144
0
{
145
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
146
0
  gssize nread;
147
148
0
  if (!buffer && blocking)
149
0
          nread = g_input_stream_skip (priv->base_stream, count, cancellable, error);
150
0
  else
151
0
          nread = g_pollable_stream_read (priv->base_stream,
152
0
                                          buffer, count,
153
0
                                          blocking,
154
0
                                          cancellable, error);
155
0
  if (nread == 0) {
156
0
    priv->eof = TRUE;
157
0
    if (priv->encoding != SOUP_ENCODING_EOF) {
158
0
      g_set_error_literal (error, G_IO_ERROR,
159
0
               G_IO_ERROR_PARTIAL_INPUT,
160
0
               _("Connection terminated unexpectedly"));
161
0
      return -1;
162
0
    }
163
0
  }
164
0
  return nread;
165
0
}
166
167
static gssize
168
soup_body_input_stream_read_chunked (SoupBodyInputStream  *bistream,
169
             void                 *buffer,
170
             gsize                 count,
171
             gboolean              blocking,
172
             GCancellable         *cancellable,
173
             GError              **error)
174
0
{
175
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
176
0
  SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (priv->base_stream);
177
0
  char metabuf[128];
178
0
  gssize nread;
179
0
        guint64 chunk_size;
180
0
        gchar *end;
181
0
        char *boundary;
182
0
  gboolean got_line;
183
184
0
again:
185
0
  switch (priv->chunked_state) {
186
0
  case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE:
187
0
    nread = soup_filter_input_stream_read_until (
188
0
      fstream, metabuf, sizeof (metabuf),
189
0
                        "\r\n", 2, blocking, TRUE,
190
0
      &got_line, cancellable, error);
191
0
    if (nread < 0)
192
0
      return nread;
193
194
0
    if (nread == 0 || !got_line || nread < 3) {
195
0
      if (error && *error == NULL) {
196
0
        g_set_error_literal (error, G_IO_ERROR,
197
0
                 G_IO_ERROR_PARTIAL_INPUT,
198
0
                 _("Connection terminated unexpectedly"));
199
0
      }
200
0
      return -1;
201
0
    }
202
203
                /* metabuf is not NUL-terminated by read_until(), so terminate
204
                 * it at the trailing CRLF before scanning for chunk extensions. */
205
0
                metabuf[nread - 2] = '\0';
206
207
                /* ignore extensions */
208
0
                boundary = memchr (metabuf, ';', nread);
209
0
                if (boundary)
210
0
                        *boundary = '\0';
211
212
0
                chunk_size = g_ascii_strtoull (metabuf, &end, 16);
213
0
                if (*end) {
214
0
                        if (error && *error == NULL) {
215
0
                                g_set_error_literal (error, G_IO_ERROR,
216
0
                                                     G_IO_ERROR_INVALID_ARGUMENT,
217
0
                                                     _("Invalid chunk size"));
218
0
                        }
219
0
                        return -1;
220
0
                }
221
222
0
                if (chunk_size > G_MAXOFFSET) {
223
0
                        if (error && *error == NULL) {
224
0
                                g_set_error_literal (error, G_IO_ERROR,
225
0
                                                     G_IO_ERROR_MESSAGE_TOO_LARGE,
226
0
                                                     _("Too large chunk"));
227
0
                        }
228
0
                        return -1;
229
0
                }
230
0
                priv->read_length = (goffset)chunk_size;
231
0
    if (priv->read_length > 0)
232
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK;
233
0
    else
234
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_TRAILERS;
235
0
    break;
236
237
0
  case SOUP_BODY_INPUT_STREAM_STATE_CHUNK:
238
0
    nread = soup_body_input_stream_read_raw (
239
0
      bistream, buffer,
240
0
      MIN (count, priv->read_length),
241
0
      blocking, cancellable, error);
242
0
    if (nread > 0) {
243
0
      priv->read_length -= nread;
244
0
      if (priv->read_length == 0)
245
0
        priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END;
246
0
    }
247
0
    return nread;
248
249
0
  case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END:
250
0
    nread = soup_filter_input_stream_read_until (
251
0
      SOUP_FILTER_INPUT_STREAM (priv->base_stream),
252
0
      metabuf, sizeof (metabuf),
253
0
                        "\r\n", 2, blocking, TRUE,
254
0
      &got_line, cancellable, error);
255
0
    if (nread < 0)
256
0
      return nread;
257
0
    if (nread == 0 || !got_line) {
258
0
      if (error && *error == NULL) {
259
0
        g_set_error_literal (error, G_IO_ERROR,
260
0
                 G_IO_ERROR_PARTIAL_INPUT,
261
0
                 _("Connection terminated unexpectedly"));
262
0
      }
263
0
      return -1;
264
0
    }
265
266
0
    priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE;
267
0
    break;
268
269
0
  case SOUP_BODY_INPUT_STREAM_STATE_TRAILERS:
270
0
    nread = soup_filter_input_stream_read_until (
271
0
      fstream, metabuf, sizeof (metabuf),
272
0
                        "\r\n", 2, blocking, TRUE,
273
0
      &got_line, cancellable, error);
274
0
    if (nread < 0)
275
0
      return nread;
276
277
0
    if (nread == 0) {
278
0
      if (error && *error == NULL) {
279
0
        g_set_error_literal (error, G_IO_ERROR,
280
0
                 G_IO_ERROR_PARTIAL_INPUT,
281
0
                 _("Connection terminated unexpectedly"));
282
0
      }
283
0
      return -1;
284
0
    }
285
286
0
    if (nread == 2 && strncmp (metabuf, "\r\n", nread) == 0) {
287
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_DONE;
288
0
      priv->eof = TRUE;
289
0
    }
290
0
    break;
291
292
0
  case SOUP_BODY_INPUT_STREAM_STATE_DONE:
293
0
    return 0;
294
0
  }
295
296
0
  goto again;
297
0
}
298
299
static gssize
300
read_internal (GInputStream  *stream,
301
         void          *buffer,
302
         gsize          count,
303
         gboolean       blocking,
304
         GCancellable  *cancellable,
305
         GError       **error)
306
0
{
307
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
308
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
309
0
  gssize nread;
310
311
0
  if (priv->eof)
312
0
    return 0;
313
314
0
  switch (priv->encoding) {
315
0
  case SOUP_ENCODING_NONE:
316
0
    return 0;
317
318
0
  case SOUP_ENCODING_CHUNKED:
319
0
    return soup_body_input_stream_read_chunked (bistream, buffer, count,
320
0
                  blocking, cancellable, error);
321
322
0
  case SOUP_ENCODING_CONTENT_LENGTH:
323
0
  case SOUP_ENCODING_EOF:
324
0
    if (priv->read_length != -1) {
325
0
      count = MIN (count, priv->read_length);
326
0
      if (count == 0)
327
0
        return 0;
328
0
    }
329
330
0
    nread = soup_body_input_stream_read_raw (bistream, buffer, count,
331
0
               blocking, cancellable, error);
332
0
    if (priv->read_length != -1 && nread > 0) {
333
0
            priv->read_length -= nread;
334
335
0
            if (priv->encoding == SOUP_ENCODING_CONTENT_LENGTH && priv->read_length == 0) {
336
0
                    priv->eof = TRUE;
337
0
            }
338
0
    }
339
340
0
    if (priv->encoding == SOUP_ENCODING_CONTENT_LENGTH)
341
0
      priv->pos += nread;
342
0
    return nread;
343
344
0
  default:
345
0
    g_return_val_if_reached (-1);
346
0
  }
347
0
}
348
349
static gssize
350
soup_body_input_stream_skip (GInputStream *stream,
351
           gsize         count,
352
           GCancellable *cancellable,
353
           GError      **error)
354
0
{
355
0
        return read_internal (stream, NULL, count, TRUE,
356
0
                              cancellable, error);
357
0
}
358
359
static gssize
360
soup_body_input_stream_read_fn (GInputStream  *stream,
361
        void          *buffer,
362
        gsize          count,
363
        GCancellable  *cancellable,
364
        GError       **error)
365
0
{
366
0
  return read_internal (stream, buffer, count, TRUE,
367
0
            cancellable, error);
368
0
}
369
370
static gboolean
371
soup_body_input_stream_close_fn (GInputStream  *stream,
372
         GCancellable  *cancellable,
373
         GError       **error)
374
0
{
375
0
  g_signal_emit (stream, signals[CLOSED], 0);
376
377
0
  return G_INPUT_STREAM_CLASS (soup_body_input_stream_parent_class)->close_fn (stream, cancellable, error);
378
0
}
379
380
static gboolean
381
soup_body_input_stream_is_readable (GPollableInputStream *stream)
382
0
{
383
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
384
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
385
386
0
  return priv->eof ||
387
0
    g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (priv->base_stream));
388
0
}
389
390
static gboolean
391
soup_body_input_stream_can_poll (GPollableInputStream *pollable)
392
0
{
393
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (pollable));
394
0
  GInputStream *base_stream = priv->base_stream;
395
396
0
  return G_IS_POLLABLE_INPUT_STREAM (base_stream) &&
397
0
    g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (base_stream));
398
0
}
399
400
static gssize
401
soup_body_input_stream_read_nonblocking (GPollableInputStream  *stream,
402
           void                  *buffer,
403
           gsize                  count,
404
           GError               **error)
405
0
{
406
0
  return read_internal (G_INPUT_STREAM (stream), buffer, count, FALSE,
407
0
            NULL, error);
408
0
}
409
410
static GSource *
411
soup_body_input_stream_create_source (GPollableInputStream *stream,
412
              GCancellable *cancellable)
413
0
{
414
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
415
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
416
0
  GSource *base_source, *pollable_source;
417
418
0
  if (priv->eof)
419
0
    base_source = g_timeout_source_new (0);
420
0
  else
421
0
    base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (priv->base_stream), cancellable);
422
0
  g_source_set_dummy_callback (base_source);
423
424
0
  pollable_source = g_pollable_source_new (G_OBJECT (stream));
425
0
  g_source_add_child_source (pollable_source, base_source);
426
0
  g_source_unref (base_source);
427
428
0
  return pollable_source;
429
0
}
430
431
static void
432
soup_body_input_stream_class_init (SoupBodyInputStreamClass *stream_class)
433
0
{
434
0
  GObjectClass *object_class = G_OBJECT_CLASS (stream_class);
435
0
  GInputStreamClass *input_stream_class = G_INPUT_STREAM_CLASS (stream_class);
436
437
0
  object_class->constructed = soup_body_input_stream_constructed;
438
0
  object_class->set_property = soup_body_input_stream_set_property;
439
0
  object_class->get_property = soup_body_input_stream_get_property;
440
441
0
  input_stream_class->skip = soup_body_input_stream_skip;
442
0
  input_stream_class->read_fn = soup_body_input_stream_read_fn;
443
0
  input_stream_class->close_fn = soup_body_input_stream_close_fn;
444
445
0
  signals[CLOSED] =
446
0
    g_signal_new ("closed",
447
0
            G_OBJECT_CLASS_TYPE (object_class),
448
0
            G_SIGNAL_RUN_LAST,
449
0
            0,
450
0
            NULL, NULL,
451
0
            NULL,
452
0
            G_TYPE_NONE, 0);
453
454
0
        properties[PROP_ENCODING] =
455
0
    g_param_spec_enum ("encoding",
456
0
           "Encoding",
457
0
           "Message body encoding",
458
0
           SOUP_TYPE_ENCODING,
459
0
           SOUP_ENCODING_NONE,
460
0
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
461
462
0
        properties[PROP_CONTENT_LENGTH] =
463
0
    g_param_spec_int64 ("content-length",
464
0
            "Content-Length",
465
0
            "Message body Content-Length",
466
0
            -1, G_MAXINT64, -1,
467
0
            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
468
469
0
        g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
470
0
}
471
472
static void
473
soup_body_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface,
474
         gpointer interface_data)
475
0
{
476
0
  pollable_interface->can_poll = soup_body_input_stream_can_poll;
477
0
  pollable_interface->is_readable = soup_body_input_stream_is_readable;
478
0
  pollable_interface->read_nonblocking = soup_body_input_stream_read_nonblocking;
479
0
  pollable_interface->create_source = soup_body_input_stream_create_source;
480
0
}
481
482
static goffset
483
soup_body_input_stream_tell (GSeekable *seekable)
484
0
{
485
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (seekable));
486
0
  return priv->pos;
487
0
}
488
489
static gboolean
490
soup_body_input_stream_can_seek (GSeekable *seekable)
491
0
{
492
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (seekable));
493
494
0
  return priv->encoding == SOUP_ENCODING_CONTENT_LENGTH
495
0
    && G_IS_SEEKABLE (priv->base_stream)
496
0
    && g_seekable_can_seek (G_SEEKABLE (priv->base_stream));
497
0
}
498
499
static gboolean
500
soup_body_input_stream_seek (GSeekable     *seekable,
501
           goffset        offset,
502
           GSeekType      type,
503
           GCancellable  *cancellable,
504
           GError       **error)
505
0
{
506
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (seekable));
507
0
  goffset position, end_position;
508
509
0
  end_position = priv->pos + priv->read_length;
510
0
  switch (type) {
511
0
  case G_SEEK_CUR:
512
0
    position = priv->pos + offset;
513
0
    break;
514
0
  case G_SEEK_SET:
515
0
    position = offset;
516
0
    break;
517
0
  case G_SEEK_END:
518
0
    position = end_position + offset;
519
0
    break;
520
0
  default:
521
0
    g_return_val_if_reached (FALSE);
522
0
  }
523
524
0
  if (position < 0 || position >= end_position) {
525
0
    g_set_error_literal (error,
526
0
             G_IO_ERROR,
527
0
             G_IO_ERROR_INVALID_ARGUMENT,
528
0
             _("Invalid seek request"));
529
0
    return FALSE;
530
0
  }
531
532
0
  if (!g_seekable_seek (G_SEEKABLE (priv->base_stream), position - priv->pos,
533
0
            G_SEEK_CUR, cancellable, error))
534
0
    return FALSE;
535
536
0
  priv->pos = position;
537
538
0
  return TRUE;
539
0
}
540
541
static gboolean
542
soup_body_input_stream_can_truncate (GSeekable *seekable)
543
0
{
544
0
  return FALSE;
545
0
}
546
547
static gboolean
548
soup_body_input_stream_truncate_fn (GSeekable     *seekable,
549
            goffset        offset,
550
            GCancellable  *cancellable,
551
            GError       **error)
552
0
{
553
0
  g_set_error_literal (error,
554
0
           G_IO_ERROR,
555
0
           G_IO_ERROR_NOT_SUPPORTED,
556
0
           _("Cannot truncate SoupBodyInputStream"));
557
0
  return FALSE;
558
0
}
559
560
static void
561
soup_body_input_stream_seekable_init (GSeekableIface *seekable_interface)
562
0
{
563
0
  seekable_interface->tell         = soup_body_input_stream_tell;
564
0
  seekable_interface->can_seek     = soup_body_input_stream_can_seek;
565
0
  seekable_interface->seek         = soup_body_input_stream_seek;
566
0
  seekable_interface->can_truncate = soup_body_input_stream_can_truncate;
567
0
  seekable_interface->truncate_fn  = soup_body_input_stream_truncate_fn;
568
0
}
569
570
GInputStream *
571
soup_body_input_stream_new (GInputStream *base_stream,
572
          SoupEncoding  encoding,
573
          goffset       content_length)
574
0
{
575
0
  if (encoding == SOUP_ENCODING_CHUNKED)
576
0
    g_return_val_if_fail (SOUP_IS_FILTER_INPUT_STREAM (base_stream), NULL);
577
578
0
  return g_object_new (SOUP_TYPE_BODY_INPUT_STREAM,
579
0
           "base-stream", base_stream,
580
0
           "close-base-stream", FALSE,
581
0
           "encoding", encoding,
582
0
           "content-length", content_length,
583
           NULL);
584
0
}