Coverage Report

Created: 2026-01-10 06:18

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
  priv->base_stream = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (bistream));
85
86
0
  if (priv->encoding == SOUP_ENCODING_NONE ||
87
0
      (priv->encoding == SOUP_ENCODING_CONTENT_LENGTH &&
88
0
       priv->read_length == 0))
89
0
    priv->eof = TRUE;
90
0
}
91
92
static void
93
soup_body_input_stream_set_property (GObject *object, guint prop_id,
94
             const GValue *value, GParamSpec *pspec)
95
0
{
96
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
97
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
98
99
0
  switch (prop_id) {
100
0
  case PROP_ENCODING:
101
0
    priv->encoding = g_value_get_enum (value);
102
0
    if (priv->encoding == SOUP_ENCODING_CHUNKED)
103
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE;
104
0
    break;
105
0
  case PROP_CONTENT_LENGTH:
106
0
    priv->read_length = g_value_get_int64 (value);
107
0
    break;
108
0
  default:
109
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
110
0
    break;
111
0
  }
112
0
}
113
114
static void
115
soup_body_input_stream_get_property (GObject *object, guint prop_id,
116
             GValue *value, GParamSpec *pspec)
117
0
{
118
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
119
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
120
121
122
0
  switch (prop_id) {
123
0
  case PROP_ENCODING:
124
0
    g_value_set_enum (value, priv->encoding);
125
0
    break;
126
0
  default:
127
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
128
0
    break;
129
0
  }
130
0
}
131
132
static gssize
133
soup_body_input_stream_read_raw (SoupBodyInputStream  *bistream,
134
         void                 *buffer,
135
         gsize                 count,
136
         gboolean              blocking,
137
         GCancellable         *cancellable,
138
         GError              **error)
139
0
{
140
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
141
0
  gssize nread;
142
143
0
  if (!buffer && blocking)
144
0
          nread = g_input_stream_skip (priv->base_stream, count, cancellable, error);
145
0
  else
146
0
          nread = g_pollable_stream_read (priv->base_stream,
147
0
                                          buffer, count,
148
0
                                          blocking,
149
0
                                          cancellable, error);
150
0
  if (nread == 0) {
151
0
    priv->eof = TRUE;
152
0
    if (priv->encoding != SOUP_ENCODING_EOF) {
153
0
      g_set_error_literal (error, G_IO_ERROR,
154
0
               G_IO_ERROR_PARTIAL_INPUT,
155
0
               _("Connection terminated unexpectedly"));
156
0
      return -1;
157
0
    }
158
0
  }
159
0
  return nread;
160
0
}
161
162
static gssize
163
soup_body_input_stream_read_chunked (SoupBodyInputStream  *bistream,
164
             void                 *buffer,
165
             gsize                 count,
166
             gboolean              blocking,
167
             GCancellable         *cancellable,
168
             GError              **error)
169
0
{
170
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
171
0
  SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (priv->base_stream);
172
0
  char metabuf[128];
173
0
  gssize nread;
174
0
  gboolean got_line;
175
176
0
again:
177
0
  switch (priv->chunked_state) {
178
0
  case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE:
179
0
    nread = soup_filter_input_stream_read_line (
180
0
      fstream, metabuf, sizeof (metabuf), blocking,
181
0
      &got_line, cancellable, error);
182
0
    if (nread < 0)
183
0
      return nread;
184
0
    if (nread == 0 || !got_line) {
185
0
      if (error && *error == NULL) {
186
0
        g_set_error_literal (error, G_IO_ERROR,
187
0
                 G_IO_ERROR_PARTIAL_INPUT,
188
0
                 _("Connection terminated unexpectedly"));
189
0
      }
190
0
      return -1;
191
0
    }
192
193
0
    priv->read_length = strtoul (metabuf, NULL, 16);
194
0
    if (priv->read_length > 0)
195
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK;
196
0
    else
197
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_TRAILERS;
198
0
    break;
199
200
0
  case SOUP_BODY_INPUT_STREAM_STATE_CHUNK:
201
0
    nread = soup_body_input_stream_read_raw (
202
0
      bistream, buffer,
203
0
      MIN (count, priv->read_length),
204
0
      blocking, cancellable, error);
205
0
    if (nread > 0) {
206
0
      priv->read_length -= nread;
207
0
      if (priv->read_length == 0)
208
0
        priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END;
209
0
    }
210
0
    return nread;
211
212
0
  case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END:
213
0
    nread = soup_filter_input_stream_read_line (
214
0
      SOUP_FILTER_INPUT_STREAM (priv->base_stream),
215
0
      metabuf, sizeof (metabuf), blocking,
216
0
      &got_line, cancellable, error);
217
0
    if (nread < 0)
218
0
      return nread;
219
0
    if (nread == 0 || !got_line) {
220
0
      if (error && *error == NULL) {
221
0
        g_set_error_literal (error, G_IO_ERROR,
222
0
                 G_IO_ERROR_PARTIAL_INPUT,
223
0
                 _("Connection terminated unexpectedly"));
224
0
      }
225
0
      return -1;
226
0
    }
227
228
0
    priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE;
229
0
    break;
230
231
0
  case SOUP_BODY_INPUT_STREAM_STATE_TRAILERS:
232
0
    nread = soup_filter_input_stream_read_line (
233
0
      fstream, metabuf, sizeof (metabuf), blocking,
234
0
      &got_line, cancellable, error);
235
0
    if (nread < 0)
236
0
      return nread;
237
238
0
    if (nread == 0) {
239
0
      if (error && *error == NULL) {
240
0
        g_set_error_literal (error, G_IO_ERROR,
241
0
                 G_IO_ERROR_PARTIAL_INPUT,
242
0
                 _("Connection terminated unexpectedly"));
243
0
      }
244
0
      return -1;
245
0
    }
246
247
0
    if ((nread == 2 && strncmp (metabuf, "\r\n", nread) == 0) || (nread == 1 && strncmp (metabuf, "\n", nread) == 0)) {
248
0
      priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_DONE;
249
0
      priv->eof = TRUE;
250
0
    }
251
0
    break;
252
253
0
  case SOUP_BODY_INPUT_STREAM_STATE_DONE:
254
0
    return 0;
255
0
  }
256
257
0
  goto again;
258
0
}
259
260
static gssize
261
read_internal (GInputStream  *stream,
262
         void          *buffer,
263
         gsize          count,
264
         gboolean       blocking,
265
         GCancellable  *cancellable,
266
         GError       **error)
267
0
{
268
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
269
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
270
0
  gssize nread;
271
272
0
  if (priv->eof)
273
0
    return 0;
274
275
0
  switch (priv->encoding) {
276
0
  case SOUP_ENCODING_NONE:
277
0
    return 0;
278
279
0
  case SOUP_ENCODING_CHUNKED:
280
0
    return soup_body_input_stream_read_chunked (bistream, buffer, count,
281
0
                  blocking, cancellable, error);
282
283
0
  case SOUP_ENCODING_CONTENT_LENGTH:
284
0
  case SOUP_ENCODING_EOF:
285
0
    if (priv->read_length != -1) {
286
0
      count = MIN (count, priv->read_length);
287
0
      if (count == 0)
288
0
        return 0;
289
0
    }
290
291
0
    nread = soup_body_input_stream_read_raw (bistream, buffer, count,
292
0
               blocking, cancellable, error);
293
0
    if (priv->read_length != -1 && nread > 0) {
294
0
            priv->read_length -= nread;
295
296
0
            if (priv->encoding == SOUP_ENCODING_CONTENT_LENGTH && priv->read_length == 0) {
297
0
                    priv->eof = TRUE;
298
0
            }
299
0
    }
300
301
0
    if (priv->encoding == SOUP_ENCODING_CONTENT_LENGTH)
302
0
      priv->pos += nread;
303
0
    return nread;
304
305
0
  default:
306
0
    g_return_val_if_reached (-1);
307
0
  }
308
0
}
309
310
static gssize
311
soup_body_input_stream_skip (GInputStream *stream,
312
           gsize         count,
313
           GCancellable *cancellable,
314
           GError      **error)
315
0
{
316
0
        return read_internal (stream, NULL, count, TRUE,
317
0
                              cancellable, error);
318
0
}
319
320
static gssize
321
soup_body_input_stream_read_fn (GInputStream  *stream,
322
        void          *buffer,
323
        gsize          count,
324
        GCancellable  *cancellable,
325
        GError       **error)
326
0
{
327
0
  return read_internal (stream, buffer, count, TRUE,
328
0
            cancellable, error);
329
0
}
330
331
static gboolean
332
soup_body_input_stream_close_fn (GInputStream  *stream,
333
         GCancellable  *cancellable,
334
         GError       **error)
335
0
{
336
0
  g_signal_emit (stream, signals[CLOSED], 0);
337
338
0
  return G_INPUT_STREAM_CLASS (soup_body_input_stream_parent_class)->close_fn (stream, cancellable, error);
339
0
}
340
341
static gboolean
342
soup_body_input_stream_is_readable (GPollableInputStream *stream)
343
0
{
344
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
345
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
346
347
0
  return priv->eof ||
348
0
    g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (priv->base_stream));
349
0
}
350
351
static gboolean
352
soup_body_input_stream_can_poll (GPollableInputStream *pollable)
353
0
{
354
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (pollable));
355
0
  GInputStream *base_stream = priv->base_stream;
356
357
0
  return G_IS_POLLABLE_INPUT_STREAM (base_stream) &&
358
0
    g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (base_stream));
359
0
}
360
361
static gssize
362
soup_body_input_stream_read_nonblocking (GPollableInputStream  *stream,
363
           void                  *buffer,
364
           gsize                  count,
365
           GError               **error)
366
0
{
367
0
  return read_internal (G_INPUT_STREAM (stream), buffer, count, FALSE,
368
0
            NULL, error);
369
0
}
370
371
static GSource *
372
soup_body_input_stream_create_source (GPollableInputStream *stream,
373
              GCancellable *cancellable)
374
0
{
375
0
  SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
376
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (bistream);
377
0
  GSource *base_source, *pollable_source;
378
379
0
  if (priv->eof)
380
0
    base_source = g_timeout_source_new (0);
381
0
  else
382
0
    base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (priv->base_stream), cancellable);
383
0
  g_source_set_dummy_callback (base_source);
384
385
0
  pollable_source = g_pollable_source_new (G_OBJECT (stream));
386
0
  g_source_add_child_source (pollable_source, base_source);
387
0
  g_source_unref (base_source);
388
389
0
  return pollable_source;
390
0
}
391
392
static void
393
soup_body_input_stream_class_init (SoupBodyInputStreamClass *stream_class)
394
0
{
395
0
  GObjectClass *object_class = G_OBJECT_CLASS (stream_class);
396
0
  GInputStreamClass *input_stream_class = G_INPUT_STREAM_CLASS (stream_class);
397
398
0
  object_class->constructed = soup_body_input_stream_constructed;
399
0
  object_class->set_property = soup_body_input_stream_set_property;
400
0
  object_class->get_property = soup_body_input_stream_get_property;
401
402
0
  input_stream_class->skip = soup_body_input_stream_skip;
403
0
  input_stream_class->read_fn = soup_body_input_stream_read_fn;
404
0
  input_stream_class->close_fn = soup_body_input_stream_close_fn;
405
406
0
  signals[CLOSED] =
407
0
    g_signal_new ("closed",
408
0
            G_OBJECT_CLASS_TYPE (object_class),
409
0
            G_SIGNAL_RUN_LAST,
410
0
            0,
411
0
            NULL, NULL,
412
0
            NULL,
413
0
            G_TYPE_NONE, 0);
414
415
0
        properties[PROP_ENCODING] =
416
0
    g_param_spec_enum ("encoding",
417
0
           "Encoding",
418
0
           "Message body encoding",
419
0
           SOUP_TYPE_ENCODING,
420
0
           SOUP_ENCODING_NONE,
421
0
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
422
423
0
        properties[PROP_CONTENT_LENGTH] =
424
0
    g_param_spec_int64 ("content-length",
425
0
            "Content-Length",
426
0
            "Message body Content-Length",
427
0
            -1, G_MAXINT64, -1,
428
0
            G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
429
430
0
        g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
431
0
}
432
433
static void
434
soup_body_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface,
435
         gpointer interface_data)
436
0
{
437
0
  pollable_interface->can_poll = soup_body_input_stream_can_poll;
438
0
  pollable_interface->is_readable = soup_body_input_stream_is_readable;
439
0
  pollable_interface->read_nonblocking = soup_body_input_stream_read_nonblocking;
440
0
  pollable_interface->create_source = soup_body_input_stream_create_source;
441
0
}
442
443
static goffset
444
soup_body_input_stream_tell (GSeekable *seekable)
445
0
{
446
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (seekable));
447
0
  return priv->pos;
448
0
}
449
450
static gboolean
451
soup_body_input_stream_can_seek (GSeekable *seekable)
452
0
{
453
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (seekable));
454
455
0
  return priv->encoding == SOUP_ENCODING_CONTENT_LENGTH
456
0
    && G_IS_SEEKABLE (priv->base_stream)
457
0
    && g_seekable_can_seek (G_SEEKABLE (priv->base_stream));
458
0
}
459
460
static gboolean
461
soup_body_input_stream_seek (GSeekable     *seekable,
462
           goffset        offset,
463
           GSeekType      type,
464
           GCancellable  *cancellable,
465
           GError       **error)
466
0
{
467
0
        SoupBodyInputStreamPrivate *priv = soup_body_input_stream_get_instance_private (SOUP_BODY_INPUT_STREAM (seekable));
468
0
  goffset position, end_position;
469
470
0
  end_position = priv->pos + priv->read_length;
471
0
  switch (type) {
472
0
  case G_SEEK_CUR:
473
0
    position = priv->pos + offset;
474
0
    break;
475
0
  case G_SEEK_SET:
476
0
    position = offset;
477
0
    break;
478
0
  case G_SEEK_END:
479
0
    position = end_position + offset;
480
0
    break;
481
0
  default:
482
0
    g_return_val_if_reached (FALSE);
483
0
  }
484
485
0
  if (position < 0 || position >= end_position) {
486
0
    g_set_error_literal (error,
487
0
             G_IO_ERROR,
488
0
             G_IO_ERROR_INVALID_ARGUMENT,
489
0
             _("Invalid seek request"));
490
0
    return FALSE;
491
0
  }
492
493
0
  if (!g_seekable_seek (G_SEEKABLE (priv->base_stream), position - priv->pos,
494
0
            G_SEEK_CUR, cancellable, error))
495
0
    return FALSE;
496
497
0
  priv->pos = position;
498
499
0
  return TRUE;
500
0
}
501
502
static gboolean
503
soup_body_input_stream_can_truncate (GSeekable *seekable)
504
0
{
505
0
  return FALSE;
506
0
}
507
508
static gboolean
509
soup_body_input_stream_truncate_fn (GSeekable     *seekable,
510
            goffset        offset,
511
            GCancellable  *cancellable,
512
            GError       **error)
513
0
{
514
0
  g_set_error_literal (error,
515
0
           G_IO_ERROR,
516
0
           G_IO_ERROR_NOT_SUPPORTED,
517
0
           _("Cannot truncate SoupBodyInputStream"));
518
0
  return FALSE;
519
0
}
520
521
static void
522
soup_body_input_stream_seekable_init (GSeekableIface *seekable_interface)
523
0
{
524
0
  seekable_interface->tell         = soup_body_input_stream_tell;
525
0
  seekable_interface->can_seek     = soup_body_input_stream_can_seek;
526
0
  seekable_interface->seek         = soup_body_input_stream_seek;
527
0
  seekable_interface->can_truncate = soup_body_input_stream_can_truncate;
528
0
  seekable_interface->truncate_fn  = soup_body_input_stream_truncate_fn;
529
0
}
530
531
GInputStream *
532
soup_body_input_stream_new (GInputStream *base_stream,
533
          SoupEncoding  encoding,
534
          goffset       content_length)
535
0
{
536
0
  if (encoding == SOUP_ENCODING_CHUNKED)
537
0
    g_return_val_if_fail (SOUP_IS_FILTER_INPUT_STREAM (base_stream), NULL);
538
539
0
  return g_object_new (SOUP_TYPE_BODY_INPUT_STREAM,
540
0
           "base-stream", base_stream,
541
0
           "close-base-stream", FALSE,
542
0
           "encoding", encoding,
543
0
           "content-length", content_length,
544
           NULL);
545
0
}