Coverage Report

Created: 2025-06-13 06:55

/src/glib/gio/gconverteroutputstream.c
Line
Count
Source (jump to first uncovered line)
1
/* GIO - GLib Input, Output and Streaming Library
2
 *
3
 * Copyright (C) 2009 Red Hat, Inc.
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1-or-later
6
 *
7
 * This library is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU Lesser General Public
9
 * License as published by the Free Software Foundation; either
10
 * version 2.1 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General
18
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19
 *
20
 * Author: Alexander Larsson <alexl@redhat.com>
21
 */
22
23
#include "config.h"
24
25
#include <string.h>
26
27
#include "gconverteroutputstream.h"
28
#include "gpollableoutputstream.h"
29
#include "gcancellable.h"
30
#include "gioenumtypes.h"
31
#include "gioerror.h"
32
#include "glibintl.h"
33
34
35
/**
36
 * SECTION:gconverteroutputstream
37
 * @short_description: Converter Output Stream
38
 * @include: gio/gio.h
39
 * @see_also: #GOutputStream, #GConverter
40
 *
41
 * Converter output stream implements #GOutputStream and allows
42
 * conversion of data of various types during reading.
43
 *
44
 * As of GLib 2.34, #GConverterOutputStream implements
45
 * #GPollableOutputStream.
46
 **/
47
48
0
#define INITIAL_BUFFER_SIZE 4096
49
50
typedef struct {
51
  char *data;
52
  gsize start;
53
  gsize end;
54
  gsize size;
55
} Buffer;
56
57
struct _GConverterOutputStreamPrivate {
58
  gboolean at_output_end;
59
  gboolean finished;
60
  GConverter *converter;
61
  Buffer output_buffer; /* To be converted and written */
62
  Buffer converted_buffer; /* Already converted */
63
};
64
65
/* Buffering strategy:
66
 *
67
 * Each time we write we must at least consume some input, or
68
 * return an error. Thus we start with writing all already
69
 * converted data and *then* we start converting (reporting
70
 * an error at any point in this).
71
 *
72
 * Its possible that what the user wrote is not enough data
73
 * for the converter, so we must then buffer it in output_buffer
74
 * and ask for more data, but we want to avoid this as much as
75
 * possible, converting directly from the users buffer.
76
 */
77
78
enum {
79
  PROP_0,
80
  PROP_CONVERTER
81
};
82
83
static void   g_converter_output_stream_set_property (GObject        *object,
84
                  guint           prop_id,
85
                  const GValue   *value,
86
                  GParamSpec     *pspec);
87
static void   g_converter_output_stream_get_property (GObject        *object,
88
                  guint           prop_id,
89
                  GValue         *value,
90
                  GParamSpec     *pspec);
91
static void   g_converter_output_stream_finalize     (GObject        *object);
92
static gssize g_converter_output_stream_write        (GOutputStream  *stream,
93
                  const void     *buffer,
94
                  gsize           count,
95
                  GCancellable   *cancellable,
96
                  GError        **error);
97
static gboolean g_converter_output_stream_flush      (GOutputStream  *stream,
98
                  GCancellable   *cancellable,
99
                  GError        **error);
100
101
static gboolean g_converter_output_stream_can_poll          (GPollableOutputStream *stream);
102
static gboolean g_converter_output_stream_is_writable       (GPollableOutputStream *stream);
103
static gssize   g_converter_output_stream_write_nonblocking (GPollableOutputStream  *stream,
104
                   const void             *buffer,
105
                   gsize                  size,
106
                   GError               **error);
107
108
static GSource *g_converter_output_stream_create_source     (GPollableOutputStream *stream,
109
                   GCancellable          *cancellable);
110
111
static void g_converter_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface);
112
113
G_DEFINE_TYPE_WITH_CODE (GConverterOutputStream,
114
       g_converter_output_stream,
115
       G_TYPE_FILTER_OUTPUT_STREAM,
116
                         G_ADD_PRIVATE (GConverterOutputStream)
117
       G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,
118
            g_converter_output_stream_pollable_iface_init))
119
120
static void
121
g_converter_output_stream_class_init (GConverterOutputStreamClass *klass)
122
0
{
123
0
  GObjectClass *object_class;
124
0
  GOutputStreamClass *istream_class;
125
126
0
  object_class = G_OBJECT_CLASS (klass);
127
0
  object_class->get_property = g_converter_output_stream_get_property;
128
0
  object_class->set_property = g_converter_output_stream_set_property;
129
0
  object_class->finalize     = g_converter_output_stream_finalize;
130
131
0
  istream_class = G_OUTPUT_STREAM_CLASS (klass);
132
0
  istream_class->write_fn = g_converter_output_stream_write;
133
0
  istream_class->flush = g_converter_output_stream_flush;
134
135
0
  g_object_class_install_property (object_class,
136
0
           PROP_CONVERTER,
137
0
           g_param_spec_object ("converter",
138
0
              P_("Converter"),
139
0
              P_("The converter object"),
140
0
              G_TYPE_CONVERTER,
141
0
              G_PARAM_READWRITE|
142
0
              G_PARAM_CONSTRUCT_ONLY|
143
0
              G_PARAM_STATIC_STRINGS));
144
145
0
}
146
147
static void
148
g_converter_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface)
149
0
{
150
0
  iface->can_poll = g_converter_output_stream_can_poll;
151
0
  iface->is_writable = g_converter_output_stream_is_writable;
152
0
  iface->write_nonblocking = g_converter_output_stream_write_nonblocking;
153
0
  iface->create_source = g_converter_output_stream_create_source;
154
0
}
155
156
static void
157
g_converter_output_stream_finalize (GObject *object)
158
0
{
159
0
  GConverterOutputStreamPrivate *priv;
160
0
  GConverterOutputStream        *stream;
161
162
0
  stream = G_CONVERTER_OUTPUT_STREAM (object);
163
0
  priv = stream->priv;
164
165
0
  g_free (priv->output_buffer.data);
166
0
  g_free (priv->converted_buffer.data);
167
0
  if (priv->converter)
168
0
    g_object_unref (priv->converter);
169
170
0
  G_OBJECT_CLASS (g_converter_output_stream_parent_class)->finalize (object);
171
0
}
172
173
static void
174
g_converter_output_stream_set_property (GObject      *object,
175
               guint         prop_id,
176
               const GValue *value,
177
               GParamSpec   *pspec)
178
0
{
179
0
  GConverterOutputStream *cstream;
180
181
0
  cstream = G_CONVERTER_OUTPUT_STREAM (object);
182
183
0
   switch (prop_id)
184
0
    {
185
0
    case PROP_CONVERTER:
186
0
      cstream->priv->converter = g_value_dup_object (value);
187
0
      break;
188
189
0
    default:
190
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
191
0
      break;
192
0
    }
193
194
0
}
195
196
static void
197
g_converter_output_stream_get_property (GObject    *object,
198
               guint       prop_id,
199
               GValue     *value,
200
               GParamSpec *pspec)
201
0
{
202
0
  GConverterOutputStreamPrivate *priv;
203
0
  GConverterOutputStream        *cstream;
204
205
0
  cstream = G_CONVERTER_OUTPUT_STREAM (object);
206
0
  priv = cstream->priv;
207
208
0
  switch (prop_id)
209
0
    {
210
0
    case PROP_CONVERTER:
211
0
      g_value_set_object (value, priv->converter);
212
0
      break;
213
214
0
    default:
215
0
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
216
0
      break;
217
0
    }
218
0
}
219
220
static void
221
g_converter_output_stream_init (GConverterOutputStream *stream)
222
0
{
223
0
  stream->priv = g_converter_output_stream_get_instance_private (stream);
224
0
}
225
226
/**
227
 * g_converter_output_stream_new:
228
 * @base_stream: a #GOutputStream
229
 * @converter: a #GConverter
230
 *
231
 * Creates a new converter output stream for the @base_stream.
232
 *
233
 * Returns: a new #GOutputStream.
234
 **/
235
GOutputStream *
236
g_converter_output_stream_new (GOutputStream *base_stream,
237
                               GConverter    *converter)
238
0
{
239
0
  GOutputStream *stream;
240
241
0
  g_return_val_if_fail (G_IS_OUTPUT_STREAM (base_stream), NULL);
242
243
0
  stream = g_object_new (G_TYPE_CONVERTER_OUTPUT_STREAM,
244
0
                         "base-stream", base_stream,
245
0
       "converter", converter,
246
0
       NULL);
247
248
0
  return stream;
249
0
}
250
251
static gsize
252
buffer_data_size (Buffer *buffer)
253
0
{
254
0
  return buffer->end - buffer->start;
255
0
}
256
257
static gsize
258
buffer_tailspace (Buffer *buffer)
259
0
{
260
0
  return buffer->size - buffer->end;
261
0
}
262
263
static char *
264
buffer_data (Buffer *buffer)
265
0
{
266
0
  return buffer->data + buffer->start;
267
0
}
268
269
static void
270
buffer_consumed (Buffer *buffer,
271
     gsize count)
272
0
{
273
0
  buffer->start += count;
274
0
  if (buffer->start == buffer->end)
275
0
    buffer->start = buffer->end = 0;
276
0
}
277
278
static void
279
compact_buffer (Buffer *buffer)
280
0
{
281
0
  gsize in_buffer;
282
283
0
  in_buffer = buffer_data_size (buffer);
284
0
  memmove (buffer->data,
285
0
     buffer->data + buffer->start,
286
0
     in_buffer);
287
0
  buffer->end -= buffer->start;
288
0
  buffer->start = 0;
289
0
}
290
291
static void
292
grow_buffer (Buffer *buffer)
293
0
{
294
0
  char *data;
295
0
  gsize size, in_buffer;
296
297
0
  if (buffer->size == 0)
298
0
    size = INITIAL_BUFFER_SIZE;
299
0
  else
300
0
    size = buffer->size * 2;
301
302
0
  data = g_malloc (size);
303
0
  in_buffer = buffer_data_size (buffer);
304
305
0
  if (in_buffer != 0)
306
0
    memcpy (data,
307
0
            buffer->data + buffer->start,
308
0
            in_buffer);
309
310
0
  g_free (buffer->data);
311
0
  buffer->data = data;
312
0
  buffer->end -= buffer->start;
313
0
  buffer->start = 0;
314
0
  buffer->size = size;
315
0
}
316
317
/* Ensures that the buffer can fit at_least_size bytes,
318
 * *including* the current in-buffer data */
319
static void
320
buffer_ensure_space (Buffer *buffer,
321
         gsize at_least_size)
322
0
{
323
0
  gsize in_buffer, left_to_fill;
324
325
0
  in_buffer = buffer_data_size (buffer);
326
327
0
  if (in_buffer >= at_least_size)
328
0
    return;
329
330
0
  left_to_fill = buffer_tailspace (buffer);
331
332
0
  if (in_buffer + left_to_fill >= at_least_size)
333
0
    {
334
      /* We fit in remaining space at end */
335
      /* If the copy is small, compact now anyway so we can fill more */
336
0
      if (in_buffer < 256)
337
0
  compact_buffer (buffer);
338
0
    }
339
0
  else if (buffer->size >= at_least_size)
340
0
    {
341
      /* We fit, but only if we compact */
342
0
      compact_buffer (buffer);
343
0
    }
344
0
  else
345
0
    {
346
      /* Need to grow buffer */
347
0
      while (buffer->size < at_least_size)
348
0
  grow_buffer (buffer);
349
0
    }
350
0
}
351
352
static void
353
buffer_append (Buffer *buffer,
354
         const char *data,
355
         gsize data_size)
356
0
{
357
0
  buffer_ensure_space (buffer,
358
0
           buffer_data_size (buffer) + data_size);
359
0
  memcpy (buffer->data + buffer->end, data, data_size);
360
0
  buffer->end += data_size;
361
0
}
362
363
364
static gboolean
365
flush_buffer (GConverterOutputStream *stream,
366
        gboolean                blocking,
367
        GCancellable           *cancellable,
368
        GError                **error)
369
0
{
370
0
  GConverterOutputStreamPrivate *priv;
371
0
  GOutputStream *base_stream;
372
0
  gsize nwritten;
373
0
  gsize available;
374
0
  gboolean res;
375
376
0
  priv = stream->priv;
377
378
0
  base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
379
380
0
  available = buffer_data_size (&priv->converted_buffer);
381
0
  if (available > 0)
382
0
    {
383
0
      res = g_pollable_stream_write_all (base_stream,
384
0
           buffer_data (&priv->converted_buffer),
385
0
           available,
386
0
           blocking,
387
0
           &nwritten,
388
0
           cancellable,
389
0
           error);
390
0
      buffer_consumed (&priv->converted_buffer, nwritten);
391
0
      return res;
392
0
    }
393
0
  return TRUE;
394
0
}
395
396
397
static gssize
398
write_internal (GOutputStream  *stream,
399
    const void     *buffer,
400
    gsize           count,
401
    gboolean        blocking,
402
    GCancellable   *cancellable,
403
    GError        **error)
404
0
{
405
0
  GConverterOutputStream *cstream;
406
0
  GConverterOutputStreamPrivate *priv;
407
0
  gssize retval;
408
0
  GConverterResult res;
409
0
  gsize bytes_read;
410
0
  gsize bytes_written;
411
0
  GError *my_error;
412
0
  const char *to_convert;
413
0
  gsize to_convert_size, converted_bytes;
414
0
  gboolean converting_from_buffer;
415
416
0
  cstream = G_CONVERTER_OUTPUT_STREAM (stream);
417
0
  priv = cstream->priv;
418
419
  /* Write out all available pre-converted data and fail if
420
     not possible */
421
0
  if (!flush_buffer (cstream, blocking, cancellable, error))
422
0
    return -1;
423
424
0
  if (priv->finished)
425
0
    return 0;
426
427
  /* Convert as much as possible */
428
0
  if (buffer_data_size (&priv->output_buffer) > 0)
429
0
    {
430
0
      converting_from_buffer = TRUE;
431
0
      buffer_append (&priv->output_buffer, buffer, count);
432
0
      to_convert = buffer_data (&priv->output_buffer);
433
0
      to_convert_size = buffer_data_size (&priv->output_buffer);
434
0
    }
435
0
  else
436
0
    {
437
0
      converting_from_buffer = FALSE;
438
0
      to_convert = buffer;
439
0
      to_convert_size = count;
440
0
    }
441
442
  /* Ensure we have *some* initial target space */
443
0
  buffer_ensure_space (&priv->converted_buffer, to_convert_size);
444
445
0
  converted_bytes = 0;
446
0
  while (!priv->finished && converted_bytes < to_convert_size)
447
0
    {
448
      /* Ensure we have *some* target space */
449
0
      if (buffer_tailspace (&priv->converted_buffer) == 0)
450
0
  grow_buffer (&priv->converted_buffer);
451
452
      /* Try to convert to our buffer */
453
0
      my_error = NULL;
454
0
      res = g_converter_convert (priv->converter,
455
0
         to_convert + converted_bytes,
456
0
         to_convert_size - converted_bytes,
457
0
         buffer_data (&priv->converted_buffer) + buffer_data_size (&priv->converted_buffer),
458
0
         buffer_tailspace (&priv->converted_buffer),
459
0
         0,
460
0
         &bytes_read,
461
0
         &bytes_written,
462
0
         &my_error);
463
464
0
      if (res != G_CONVERTER_ERROR)
465
0
  {
466
0
    priv->converted_buffer.end += bytes_written;
467
0
    converted_bytes += bytes_read;
468
469
0
    if (res == G_CONVERTER_FINISHED)
470
0
      priv->finished = TRUE;
471
0
  }
472
0
      else
473
0
  {
474
    /* No-space errors can be handled locally: */
475
0
    if (g_error_matches (my_error,
476
0
             G_IO_ERROR,
477
0
             G_IO_ERROR_NO_SPACE))
478
0
      {
479
        /* Need more destination space, grow it
480
         * Note: if we actually grow the buffer (as opposed to compacting it),
481
         * this will double the size, not just add one byte. */
482
0
        buffer_ensure_space (&priv->converted_buffer,
483
0
           priv->converted_buffer.size + 1);
484
0
        g_error_free (my_error);
485
0
        continue;
486
0
      }
487
488
0
    if (converted_bytes > 0)
489
0
      {
490
        /* We got a conversion error, but we did convert some bytes before
491
     that, so handle those before reporting the error */
492
0
        g_error_free (my_error);
493
0
        break;
494
0
      }
495
496
0
    if (g_error_matches (my_error,
497
0
             G_IO_ERROR,
498
0
             G_IO_ERROR_PARTIAL_INPUT))
499
0
      {
500
        /* Consume everything to buffer that we append to next time
501
     we write */
502
0
        if (!converting_from_buffer)
503
0
    buffer_append (&priv->output_buffer, buffer, count);
504
        /* in the converting_from_buffer case we already appended this */
505
506
0
              g_error_free (my_error);
507
0
        return count; /* consume everything */
508
0
      }
509
510
    /* Converted no data and got a normal error, return it */
511
0
    g_propagate_error (error, my_error);
512
0
    return -1;
513
0
  }
514
0
    }
515
516
0
  if (converting_from_buffer)
517
0
    {
518
0
      buffer_consumed (&priv->output_buffer, converted_bytes);
519
0
      retval = count;
520
0
    }
521
0
  else
522
0
    retval = converted_bytes;
523
524
  /* We now successfully consumed retval bytes, so we can't return an error,
525
     even if writing this to the base stream fails. If it does we'll just
526
     stop early and report this error when we try again on the next
527
     write call. */
528
0
  flush_buffer (cstream, blocking, cancellable, NULL);
529
530
0
  return retval;
531
0
}
532
533
static gssize
534
g_converter_output_stream_write (GOutputStream  *stream,
535
         const void     *buffer,
536
         gsize           count,
537
         GCancellable   *cancellable,
538
         GError        **error)
539
0
{
540
0
  return write_internal (stream, buffer, count, TRUE, cancellable, error);
541
0
}
542
543
static gboolean
544
g_converter_output_stream_flush (GOutputStream  *stream,
545
         GCancellable   *cancellable,
546
         GError        **error)
547
0
{
548
0
  GConverterOutputStream *cstream;
549
0
  GConverterOutputStreamPrivate *priv;
550
0
  GConverterResult res;
551
0
  GError *my_error;
552
0
  gboolean is_closing;
553
0
  gboolean flushed;
554
0
  gsize bytes_read;
555
0
  gsize bytes_written;
556
557
0
  cstream = G_CONVERTER_OUTPUT_STREAM (stream);
558
0
  priv = cstream->priv;
559
560
0
  is_closing = g_output_stream_is_closing (stream);
561
562
  /* Write out all available pre-converted data and fail if
563
     not possible */
564
0
  if (!flush_buffer (cstream, TRUE, cancellable, error))
565
0
    return FALSE;
566
567
  /* Ensure we have *some* initial target space */
568
0
  buffer_ensure_space (&priv->converted_buffer, 1);
569
570
  /* Convert whole buffer */
571
0
  flushed = FALSE;
572
0
  while (!priv->finished && !flushed)
573
0
    {
574
      /* Ensure we have *some* target space */
575
0
      if (buffer_tailspace (&priv->converted_buffer) == 0)
576
0
  grow_buffer (&priv->converted_buffer);
577
578
      /* Try to convert to our buffer */
579
0
      my_error = NULL;
580
0
      res = g_converter_convert (priv->converter,
581
0
         buffer_data (&priv->output_buffer),
582
0
         buffer_data_size (&priv->output_buffer),
583
0
         buffer_data (&priv->converted_buffer) + buffer_data_size (&priv->converted_buffer),
584
0
         buffer_tailspace (&priv->converted_buffer),
585
0
         is_closing ? G_CONVERTER_INPUT_AT_END : G_CONVERTER_FLUSH,
586
0
         &bytes_read,
587
0
         &bytes_written,
588
0
         &my_error);
589
590
0
      if (res != G_CONVERTER_ERROR)
591
0
  {
592
0
    priv->converted_buffer.end += bytes_written;
593
0
    buffer_consumed (&priv->output_buffer, bytes_read);
594
595
0
    if (res == G_CONVERTER_FINISHED)
596
0
      priv->finished = TRUE;
597
0
    if (!is_closing &&
598
0
        res == G_CONVERTER_FLUSHED)
599
0
      {
600
        /* Should not have returned FLUSHED with input left */
601
0
        g_assert (buffer_data_size (&priv->output_buffer) == 0);
602
0
        flushed = TRUE;
603
0
      }
604
0
  }
605
0
      else
606
0
  {
607
    /* No-space errors can be handled locally: */
608
0
    if (g_error_matches (my_error,
609
0
             G_IO_ERROR,
610
0
             G_IO_ERROR_NO_SPACE))
611
0
      {
612
        /* Need more destination space, grow it
613
         * Note: if we actually grow the buffer (as opposed to compacting it),
614
         * this will double the size, not just add one byte. */
615
0
        buffer_ensure_space (&priv->converted_buffer,
616
0
           priv->converted_buffer.size + 1);
617
0
        g_error_free (my_error);
618
0
        continue;
619
0
      }
620
621
    /* Any other error, including PARTIAL_INPUT can't be fixed by now
622
       and is an error */
623
0
    g_propagate_error (error, my_error);
624
0
    return FALSE;
625
0
  }
626
0
    }
627
628
  /* Now write all converted data to base stream */
629
0
  if (!flush_buffer (cstream, TRUE, cancellable, error))
630
0
    return FALSE;
631
632
0
  return TRUE;
633
0
}
634
635
static gboolean
636
g_converter_output_stream_can_poll (GPollableOutputStream *stream)
637
0
{
638
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
639
640
0
  return (G_IS_POLLABLE_OUTPUT_STREAM (base_stream) &&
641
0
    g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (base_stream)));
642
0
}
643
644
static gboolean
645
g_converter_output_stream_is_writable (GPollableOutputStream *stream)
646
0
{
647
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
648
649
0
  return g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (base_stream));
650
0
}
651
652
static gssize
653
g_converter_output_stream_write_nonblocking (GPollableOutputStream  *stream,
654
               const void             *buffer,
655
               gsize                   count,
656
               GError                **error)
657
0
{
658
0
  return write_internal (G_OUTPUT_STREAM (stream), buffer, count, FALSE,
659
0
       NULL, error);
660
0
}
661
662
static GSource *
663
g_converter_output_stream_create_source (GPollableOutputStream *stream,
664
           GCancellable          *cancellable)
665
0
{
666
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
667
0
  GSource *base_source, *pollable_source;
668
669
0
  base_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (base_stream), NULL);
670
0
  pollable_source = g_pollable_source_new_full (stream, base_source,
671
0
            cancellable);
672
0
  g_source_unref (base_source);
673
674
0
  return pollable_source;
675
0
}
676
677
/**
678
 * g_converter_output_stream_get_converter:
679
 * @converter_stream: a #GConverterOutputStream
680
 *
681
 * Gets the #GConverter that is used by @converter_stream.
682
 *
683
 * Returns: (transfer none): the converter of the converter output stream
684
 *
685
 * Since: 2.24
686
 */
687
GConverter *
688
g_converter_output_stream_get_converter (GConverterOutputStream *converter_stream)
689
0
{
690
0
  return converter_stream->priv->converter;
691
0
}