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