Coverage Report

Created: 2025-07-23 08:13

/src/pango/subprojects/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
 * GConverterOutputStream:
37
 *
38
 * Converter output stream implements [class@Gio.OutputStream] and allows
39
 * conversion of data of various types during reading.
40
 *
41
 * As of GLib 2.34, `GConverterOutputStream` implements
42
 * [iface@Gio.PollableOutputStream].
43
 */
44
45
0
#define INITIAL_BUFFER_SIZE 4096
46
47
typedef struct {
48
  char *data;
49
  gsize start;
50
  gsize end;
51
  gsize size;
52
} Buffer;
53
54
struct _GConverterOutputStreamPrivate {
55
  gboolean at_output_end;
56
  gboolean finished;
57
  GConverter *converter;
58
  Buffer output_buffer; /* To be converted and written */
59
  Buffer converted_buffer; /* Already converted */
60
};
61
62
/* Buffering strategy:
63
 *
64
 * Each time we write we must at least consume some input, or
65
 * return an error. Thus we start with writing all already
66
 * converted data and *then* we start converting (reporting
67
 * an error at any point in this).
68
 *
69
 * Its possible that what the user wrote is not enough data
70
 * for the converter, so we must then buffer it in output_buffer
71
 * and ask for more data, but we want to avoid this as much as
72
 * possible, converting directly from the users buffer.
73
 */
74
75
enum {
76
  PROP_0,
77
  PROP_CONVERTER
78
};
79
80
static void   g_converter_output_stream_set_property (GObject        *object,
81
                  guint           prop_id,
82
                  const GValue   *value,
83
                  GParamSpec     *pspec);
84
static void   g_converter_output_stream_get_property (GObject        *object,
85
                  guint           prop_id,
86
                  GValue         *value,
87
                  GParamSpec     *pspec);
88
static void   g_converter_output_stream_finalize     (GObject        *object);
89
static gssize g_converter_output_stream_write        (GOutputStream  *stream,
90
                  const void     *buffer,
91
                  gsize           count,
92
                  GCancellable   *cancellable,
93
                  GError        **error);
94
static gboolean g_converter_output_stream_flush      (GOutputStream  *stream,
95
                  GCancellable   *cancellable,
96
                  GError        **error);
97
98
static gboolean g_converter_output_stream_can_poll          (GPollableOutputStream *stream);
99
static gboolean g_converter_output_stream_is_writable       (GPollableOutputStream *stream);
100
static gssize   g_converter_output_stream_write_nonblocking (GPollableOutputStream  *stream,
101
                   const void             *buffer,
102
                   gsize                  size,
103
                   GError               **error);
104
105
static GSource *g_converter_output_stream_create_source     (GPollableOutputStream *stream,
106
                   GCancellable          *cancellable);
107
108
static void g_converter_output_stream_pollable_iface_init (GPollableOutputStreamInterface *iface);
109
110
G_DEFINE_TYPE_WITH_CODE (GConverterOutputStream,
111
       g_converter_output_stream,
112
       G_TYPE_FILTER_OUTPUT_STREAM,
113
                         G_ADD_PRIVATE (GConverterOutputStream)
114
       G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,
115
            g_converter_output_stream_pollable_iface_init))
116
117
static void
118
g_converter_output_stream_class_init (GConverterOutputStreamClass *klass)
119
0
{
120
0
  GObjectClass *object_class;
121
0
  GOutputStreamClass *istream_class;
122
123
0
  object_class = G_OBJECT_CLASS (klass);
124
0
  object_class->get_property = g_converter_output_stream_get_property;
125
0
  object_class->set_property = g_converter_output_stream_set_property;
126
0
  object_class->finalize     = g_converter_output_stream_finalize;
127
128
0
  istream_class = G_OUTPUT_STREAM_CLASS (klass);
129
0
  istream_class->write_fn = g_converter_output_stream_write;
130
0
  istream_class->flush = g_converter_output_stream_flush;
131
132
  /**
133
   * GConverterOutputStream:converter:
134
   *
135
   * The converter object.
136
   */
137
0
  g_object_class_install_property (object_class,
138
0
           PROP_CONVERTER,
139
0
           g_param_spec_object ("converter", NULL, NULL,
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
    {
426
0
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_MESSAGE_TOO_LARGE,
427
0
                           _("Unexpected data after end of conversion"));
428
0
      return -1;
429
0
    }
430
431
  /* Convert as much as possible */
432
0
  if (buffer_data_size (&priv->output_buffer) > 0)
433
0
    {
434
0
      converting_from_buffer = TRUE;
435
0
      buffer_append (&priv->output_buffer, buffer, count);
436
0
      to_convert = buffer_data (&priv->output_buffer);
437
0
      to_convert_size = buffer_data_size (&priv->output_buffer);
438
0
    }
439
0
  else
440
0
    {
441
0
      converting_from_buffer = FALSE;
442
0
      to_convert = buffer;
443
0
      to_convert_size = count;
444
0
    }
445
446
  /* Ensure we have *some* initial target space */
447
0
  buffer_ensure_space (&priv->converted_buffer, to_convert_size);
448
449
0
  converted_bytes = 0;
450
0
  while (!priv->finished && converted_bytes < to_convert_size)
451
0
    {
452
      /* Ensure we have *some* target space */
453
0
      if (buffer_tailspace (&priv->converted_buffer) == 0)
454
0
  grow_buffer (&priv->converted_buffer);
455
456
      /* Try to convert to our buffer */
457
0
      my_error = NULL;
458
0
      res = g_converter_convert (priv->converter,
459
0
         to_convert + converted_bytes,
460
0
         to_convert_size - converted_bytes,
461
0
         buffer_data (&priv->converted_buffer) + buffer_data_size (&priv->converted_buffer),
462
0
         buffer_tailspace (&priv->converted_buffer),
463
0
         0,
464
0
         &bytes_read,
465
0
         &bytes_written,
466
0
         &my_error);
467
468
0
      if (res != G_CONVERTER_ERROR)
469
0
  {
470
0
    priv->converted_buffer.end += bytes_written;
471
0
    converted_bytes += bytes_read;
472
473
0
    if (res == G_CONVERTER_FINISHED)
474
0
      priv->finished = TRUE;
475
0
  }
476
0
      else
477
0
  {
478
    /* No-space errors can be handled locally: */
479
0
    if (g_error_matches (my_error,
480
0
             G_IO_ERROR,
481
0
             G_IO_ERROR_NO_SPACE))
482
0
      {
483
        /* Need more destination space, grow it
484
         * Note: if we actually grow the buffer (as opposed to compacting it),
485
         * this will double the size, not just add one byte. */
486
0
        buffer_ensure_space (&priv->converted_buffer,
487
0
           priv->converted_buffer.size + 1);
488
0
        g_error_free (my_error);
489
0
        continue;
490
0
      }
491
492
0
    if (converted_bytes > 0)
493
0
      {
494
        /* We got a conversion error, but we did convert some bytes before
495
     that, so handle those before reporting the error */
496
0
        g_error_free (my_error);
497
0
        break;
498
0
      }
499
500
0
    if (g_error_matches (my_error,
501
0
             G_IO_ERROR,
502
0
             G_IO_ERROR_PARTIAL_INPUT))
503
0
      {
504
        /* Consume everything to buffer that we append to next time
505
     we write */
506
0
        if (!converting_from_buffer)
507
0
    buffer_append (&priv->output_buffer, buffer, count);
508
        /* in the converting_from_buffer case we already appended this */
509
510
0
              g_error_free (my_error);
511
0
        return count; /* consume everything */
512
0
      }
513
514
    /* Converted no data and got a normal error, return it */
515
0
    g_propagate_error (error, my_error);
516
0
    return -1;
517
0
  }
518
0
    }
519
520
0
  if (converting_from_buffer)
521
0
    {
522
0
      buffer_consumed (&priv->output_buffer, converted_bytes);
523
0
      retval = count;
524
0
    }
525
0
  else
526
0
    retval = converted_bytes;
527
528
  /* We now successfully consumed retval bytes, so we can't return an error,
529
     even if writing this to the base stream fails. If it does we'll just
530
     stop early and report this error when we try again on the next
531
     write call. */
532
0
  flush_buffer (cstream, blocking, cancellable, NULL);
533
534
0
  return retval;
535
0
}
536
537
static gssize
538
g_converter_output_stream_write (GOutputStream  *stream,
539
         const void     *buffer,
540
         gsize           count,
541
         GCancellable   *cancellable,
542
         GError        **error)
543
0
{
544
0
  return write_internal (stream, buffer, count, TRUE, cancellable, error);
545
0
}
546
547
static gboolean
548
g_converter_output_stream_flush (GOutputStream  *stream,
549
         GCancellable   *cancellable,
550
         GError        **error)
551
0
{
552
0
  GConverterOutputStream *cstream;
553
0
  GConverterOutputStreamPrivate *priv;
554
0
  GConverterResult res;
555
0
  GError *my_error;
556
0
  gboolean is_closing;
557
0
  gboolean flushed;
558
0
  gsize bytes_read;
559
0
  gsize bytes_written;
560
561
0
  cstream = G_CONVERTER_OUTPUT_STREAM (stream);
562
0
  priv = cstream->priv;
563
564
0
  is_closing = g_output_stream_is_closing (stream);
565
566
  /* Write out all available pre-converted data and fail if
567
     not possible */
568
0
  if (!flush_buffer (cstream, TRUE, cancellable, error))
569
0
    return FALSE;
570
571
  /* Ensure we have *some* initial target space */
572
0
  buffer_ensure_space (&priv->converted_buffer, 1);
573
574
  /* Convert whole buffer */
575
0
  flushed = FALSE;
576
0
  while (!priv->finished && !flushed)
577
0
    {
578
      /* Ensure we have *some* target space */
579
0
      if (buffer_tailspace (&priv->converted_buffer) == 0)
580
0
  grow_buffer (&priv->converted_buffer);
581
582
      /* Try to convert to our buffer */
583
0
      my_error = NULL;
584
0
      res = g_converter_convert (priv->converter,
585
0
         buffer_data (&priv->output_buffer),
586
0
         buffer_data_size (&priv->output_buffer),
587
0
         buffer_data (&priv->converted_buffer) + buffer_data_size (&priv->converted_buffer),
588
0
         buffer_tailspace (&priv->converted_buffer),
589
0
         is_closing ? G_CONVERTER_INPUT_AT_END : G_CONVERTER_FLUSH,
590
0
         &bytes_read,
591
0
         &bytes_written,
592
0
         &my_error);
593
594
0
      if (res != G_CONVERTER_ERROR)
595
0
  {
596
0
    priv->converted_buffer.end += bytes_written;
597
0
    buffer_consumed (&priv->output_buffer, bytes_read);
598
599
0
    if (res == G_CONVERTER_FINISHED)
600
0
      priv->finished = TRUE;
601
0
    if (!is_closing &&
602
0
        res == G_CONVERTER_FLUSHED)
603
0
      {
604
        /* Should not have returned FLUSHED with input left */
605
0
        g_assert (buffer_data_size (&priv->output_buffer) == 0);
606
0
        flushed = TRUE;
607
0
      }
608
0
  }
609
0
      else
610
0
  {
611
    /* No-space errors can be handled locally: */
612
0
    if (g_error_matches (my_error,
613
0
             G_IO_ERROR,
614
0
             G_IO_ERROR_NO_SPACE))
615
0
      {
616
        /* Need more destination space, grow it
617
         * Note: if we actually grow the buffer (as opposed to compacting it),
618
         * this will double the size, not just add one byte. */
619
0
        buffer_ensure_space (&priv->converted_buffer,
620
0
           priv->converted_buffer.size + 1);
621
0
        g_error_free (my_error);
622
0
        continue;
623
0
      }
624
625
    /* Any other error, including PARTIAL_INPUT can't be fixed by now
626
       and is an error */
627
0
    g_propagate_error (error, my_error);
628
0
    return FALSE;
629
0
  }
630
0
    }
631
632
  /* Now write all converted data to base stream */
633
0
  if (!flush_buffer (cstream, TRUE, cancellable, error))
634
0
    return FALSE;
635
636
0
  return TRUE;
637
0
}
638
639
static gboolean
640
g_converter_output_stream_can_poll (GPollableOutputStream *stream)
641
0
{
642
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
643
644
0
  return (G_IS_POLLABLE_OUTPUT_STREAM (base_stream) &&
645
0
    g_pollable_output_stream_can_poll (G_POLLABLE_OUTPUT_STREAM (base_stream)));
646
0
}
647
648
static gboolean
649
g_converter_output_stream_is_writable (GPollableOutputStream *stream)
650
0
{
651
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
652
653
0
  return g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (base_stream));
654
0
}
655
656
static gssize
657
g_converter_output_stream_write_nonblocking (GPollableOutputStream  *stream,
658
               const void             *buffer,
659
               gsize                   count,
660
               GError                **error)
661
0
{
662
0
  return write_internal (G_OUTPUT_STREAM (stream), buffer, count, FALSE,
663
0
       NULL, error);
664
0
}
665
666
static GSource *
667
g_converter_output_stream_create_source (GPollableOutputStream *stream,
668
           GCancellable          *cancellable)
669
0
{
670
0
  GOutputStream *base_stream = G_FILTER_OUTPUT_STREAM (stream)->base_stream;
671
0
  GSource *base_source, *pollable_source;
672
673
0
  base_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (base_stream), NULL);
674
0
  pollable_source = g_pollable_source_new_full (stream, base_source,
675
0
            cancellable);
676
0
  g_source_unref (base_source);
677
678
0
  return pollable_source;
679
0
}
680
681
/**
682
 * g_converter_output_stream_get_converter:
683
 * @converter_stream: a #GConverterOutputStream
684
 *
685
 * Gets the #GConverter that is used by @converter_stream.
686
 *
687
 * Returns: (transfer none): the converter of the converter output stream
688
 *
689
 * Since: 2.24
690
 */
691
GConverter *
692
g_converter_output_stream_get_converter (GConverterOutputStream *converter_stream)
693
0
{
694
0
  return converter_stream->priv->converter;
695
0
}