soup-message-io: use gio streams rather than SoupSocket
[platform/upstream/libsoup.git] / libsoup / soup-body-input-stream.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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 #include <string.h>
14 #include <gio/gio.h>
15
16 #include <glib/gi18n-lib.h>
17
18 #include "soup-body-input-stream.h"
19 #include "soup-enum-types.h"
20 #include "soup-filter-input-stream.h"
21 #include "soup-message-headers.h"
22
23 typedef enum {
24         SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE,
25         SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END,
26         SOUP_BODY_INPUT_STREAM_STATE_CHUNK,
27         SOUP_BODY_INPUT_STREAM_STATE_TRAILERS,
28         SOUP_BODY_INPUT_STREAM_STATE_DONE
29 } SoupBodyInputStreamState;
30
31 struct _SoupBodyInputStreamPrivate {
32         GInputStream *base_stream;
33
34         SoupEncoding  encoding;
35         goffset       read_length;
36         SoupBodyInputStreamState chunked_state;
37         gboolean      eof;
38 };
39
40 enum {
41         PROP_0,
42
43         PROP_ENCODING,
44         PROP_CONTENT_LENGTH
45 };
46
47 static void soup_body_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data);
48
49 G_DEFINE_TYPE_WITH_CODE (SoupBodyInputStream, soup_body_input_stream, G_TYPE_FILTER_INPUT_STREAM,
50                          G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM,
51                                                 soup_body_input_stream_pollable_init))
52
53 static void
54 soup_body_input_stream_init (SoupBodyInputStream *bistream)
55 {
56         bistream->priv = G_TYPE_INSTANCE_GET_PRIVATE (bistream,
57                                                       SOUP_TYPE_BODY_INPUT_STREAM,
58                                                       SoupBodyInputStreamPrivate);
59         bistream->priv->encoding = SOUP_ENCODING_NONE;
60 }
61
62 static void
63 constructed (GObject *object)
64 {
65         SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
66
67         bistream->priv->base_stream = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (bistream));
68
69         if (bistream->priv->encoding == SOUP_ENCODING_NONE ||
70             (bistream->priv->encoding == SOUP_ENCODING_CONTENT_LENGTH &&
71              bistream->priv->read_length == 0))
72                 bistream->priv->eof = TRUE;
73 }
74
75 static void
76 set_property (GObject *object, guint prop_id,
77               const GValue *value, GParamSpec *pspec)
78 {
79         SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
80
81         switch (prop_id) {
82         case PROP_ENCODING:
83                 bistream->priv->encoding = g_value_get_enum (value);
84                 if (bistream->priv->encoding == SOUP_ENCODING_CHUNKED)
85                         bistream->priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE;
86                 break;
87         case PROP_CONTENT_LENGTH:
88                 bistream->priv->read_length = g_value_get_int64 (value);
89                 break;
90         default:
91                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
92                 break;
93         }
94 }
95
96 static void
97 get_property (GObject *object, guint prop_id,
98               GValue *value, GParamSpec *pspec)
99 {
100         SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (object);
101
102         switch (prop_id) {
103         case PROP_ENCODING:
104                 g_value_set_enum (value, bistream->priv->encoding);
105                 break;
106         default:
107                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
108                 break;
109         }
110 }
111
112 static gssize
113 soup_body_input_stream_read_raw (SoupBodyInputStream  *bistream,
114                                  void                 *buffer,
115                                  gsize                 count,
116                                  gboolean              blocking,
117                                  GCancellable         *cancellable,
118                                  GError              **error)
119 {
120         gssize nread;
121
122         nread = g_pollable_stream_read (bistream->priv->base_stream,
123                                         buffer, count,
124                                         blocking,
125                                         cancellable, error);
126         if (nread == 0) {
127                 bistream->priv->eof = TRUE;
128                 if (bistream->priv->encoding != SOUP_ENCODING_EOF) {
129                         g_set_error_literal (error, G_IO_ERROR,
130                                              G_IO_ERROR_PARTIAL_INPUT,
131                                              _("Connection terminated unexpectedly"));
132                         return -1;
133                 }
134         }
135         return nread;
136 }
137
138 static gssize
139 soup_body_input_stream_read_chunked (SoupBodyInputStream  *bistream,
140                                      void                 *buffer,
141                                      gsize                 count,
142                                      gboolean              blocking,
143                                      GCancellable         *cancellable,
144                                      GError              **error)
145 {
146         SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (bistream->priv->base_stream);
147         char metabuf[128];
148         gssize nread;
149         gboolean got_line;
150
151 again:
152         switch (bistream->priv->chunked_state) {
153         case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE:
154                 nread = soup_filter_input_stream_read_line (
155                         fstream, metabuf, sizeof (metabuf), blocking,
156                         &got_line, cancellable, error);
157                 if (nread <= 0)
158                         return nread;
159                 if (!got_line) {
160                         g_set_error_literal (error, G_IO_ERROR,
161                                              G_IO_ERROR_PARTIAL_INPUT,
162                                              _("Connection terminated unexpectedly"));
163                         return -1;
164                 }
165
166                 bistream->priv->read_length = strtoul (metabuf, NULL, 16);
167                 if (bistream->priv->read_length > 0)
168                         bistream->priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK;
169                 else
170                         bistream->priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_TRAILERS;
171                 break;
172
173         case SOUP_BODY_INPUT_STREAM_STATE_CHUNK:
174                 nread = soup_body_input_stream_read_raw (
175                         bistream, buffer,
176                         MIN (count, bistream->priv->read_length),
177                         blocking, cancellable, error);
178                 if (nread > 0) {
179                         bistream->priv->read_length -= nread;
180                         if (bistream->priv->read_length == 0)
181                                 bistream->priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END;
182                 }
183                 return nread;
184
185         case SOUP_BODY_INPUT_STREAM_STATE_CHUNK_END:
186                 nread = soup_filter_input_stream_read_line (
187                         SOUP_FILTER_INPUT_STREAM (bistream->priv->base_stream),
188                         metabuf, sizeof (metabuf), blocking,
189                         &got_line, cancellable, error);
190                 if (nread <= 0)
191                         return nread;
192                 if (!got_line) {
193                         g_set_error_literal (error, G_IO_ERROR,
194                                              G_IO_ERROR_PARTIAL_INPUT,
195                                              _("Connection terminated unexpectedly"));
196                         return -1;
197                 }
198
199                 bistream->priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE;
200                 break;
201
202         case SOUP_BODY_INPUT_STREAM_STATE_TRAILERS:
203                 nread = soup_filter_input_stream_read_line (
204                         fstream, buffer, count, blocking,
205                         &got_line, cancellable, error);
206                 if (nread <= 0)
207                         return nread;
208
209                 if (strncmp (buffer, "\r\n", nread) || strncmp (buffer, "\n", nread))
210                         bistream->priv->chunked_state = SOUP_BODY_INPUT_STREAM_STATE_DONE;
211                 break;
212
213         case SOUP_BODY_INPUT_STREAM_STATE_DONE:
214                 return 0;
215         }
216
217         goto again;
218 }
219
220 static gssize
221 read_internal (GInputStream  *stream,
222                void          *buffer,
223                gsize          count,
224                gboolean       blocking,
225                GCancellable  *cancellable,
226                GError       **error)
227 {
228         SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
229         gssize nread;
230
231         if (bistream->priv->eof)
232                 return 0;
233
234         switch (bistream->priv->encoding) {
235         case SOUP_ENCODING_NONE:
236                 return 0;
237
238         case SOUP_ENCODING_CHUNKED:
239                 return soup_body_input_stream_read_chunked (bistream, buffer, count,
240                                                             blocking, cancellable, error);
241
242         case SOUP_ENCODING_CONTENT_LENGTH:
243         case SOUP_ENCODING_EOF:
244                 if (bistream->priv->read_length != -1) {
245                         count = MIN (count, bistream->priv->read_length);
246                         if (count == 0)
247                                 return 0;
248                 }
249
250                 nread = soup_body_input_stream_read_raw (bistream, buffer, count,
251                                                          blocking, cancellable, error);
252                 if (bistream->priv->read_length != -1 && nread > 0)
253                         bistream->priv->read_length -= nread;
254                 return nread;
255
256         default:
257                 g_return_val_if_reached (-1);
258         }
259 }
260
261 static gssize
262 soup_body_input_stream_read_fn (GInputStream  *stream,
263                                 void          *buffer,
264                                 gsize          count,
265                                 GCancellable  *cancellable,
266                                 GError       **error)
267 {
268         return read_internal (stream, buffer, count, TRUE,
269                               cancellable, error);
270 }
271
272 static gboolean
273 soup_body_input_stream_is_readable (GPollableInputStream *stream)
274 {
275         SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
276
277         return bistream->priv->eof ||
278                 g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (bistream->priv->base_stream));
279 }
280
281 static gssize
282 soup_body_input_stream_read_nonblocking (GPollableInputStream  *stream,
283                                          void                  *buffer,
284                                          gsize                  count,
285                                          GError               **error)
286 {
287         return read_internal (G_INPUT_STREAM (stream), buffer, count, FALSE,
288                               NULL, error);
289 }
290
291 static GSource *
292 soup_body_input_stream_create_source (GPollableInputStream *stream,
293                                       GCancellable *cancellable)
294 {
295         SoupBodyInputStream *bistream = SOUP_BODY_INPUT_STREAM (stream);
296         GSource *base_source, *pollable_source;
297
298         if (bistream->priv->eof)
299                 base_source = g_timeout_source_new (0);
300         else
301                 base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (bistream->priv->base_stream), cancellable);
302         g_source_set_dummy_callback (base_source);
303
304         pollable_source = g_pollable_source_new (G_OBJECT (stream));
305         g_source_add_child_source (pollable_source, base_source);
306         g_source_unref (base_source);
307
308         return pollable_source;
309 }
310
311 static void
312 soup_body_input_stream_class_init (SoupBodyInputStreamClass *stream_class)
313 {
314         GObjectClass *object_class = G_OBJECT_CLASS (stream_class);
315         GInputStreamClass *input_stream_class = G_INPUT_STREAM_CLASS (stream_class);
316
317         g_type_class_add_private (stream_class, sizeof (SoupBodyInputStreamPrivate));
318
319         object_class->constructed = constructed;
320         object_class->set_property = set_property;
321         object_class->get_property = get_property;
322
323         input_stream_class->read_fn = soup_body_input_stream_read_fn;
324
325         g_object_class_install_property (
326                 object_class, PROP_ENCODING,
327                 g_param_spec_enum ("encoding",
328                                    "Encoding",
329                                    "Message body encoding",
330                                    SOUP_TYPE_ENCODING,
331                                    SOUP_ENCODING_NONE,
332                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
333         g_object_class_install_property (
334                 object_class, PROP_CONTENT_LENGTH,
335                 g_param_spec_int64 ("content-length",
336                                     "Content-Length",
337                                     "Message body Content-Length",
338                                     -1, G_MAXINT64, -1,
339                                     G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
340 }
341
342 static void
343 soup_body_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface,
344                                  gpointer interface_data)
345 {
346         pollable_interface->is_readable = soup_body_input_stream_is_readable;
347         pollable_interface->read_nonblocking = soup_body_input_stream_read_nonblocking;
348         pollable_interface->create_source = soup_body_input_stream_create_source;
349 }
350
351 GInputStream *
352 soup_body_input_stream_new (SoupFilterInputStream *base_stream,
353                             SoupEncoding           encoding,
354                             goffset                content_length)
355 {
356         return g_object_new (SOUP_TYPE_BODY_INPUT_STREAM,
357                              "base-stream", base_stream,
358                              "close-base-stream", FALSE,
359                              "encoding", encoding,
360                              "content-length", content_length,
361                              NULL);
362 }