Remove extern "C" wrapping other includes
[platform/upstream/libsoup.git] / libsoup / soup-body-output-stream.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-body-output-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 <string.h>
13
14 #include "soup-body-output-stream.h"
15 #include "soup.h"
16
17 typedef enum {
18         SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_SIZE,
19         SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_END,
20         SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK,
21         SOUP_BODY_OUTPUT_STREAM_STATE_TRAILERS,
22         SOUP_BODY_OUTPUT_STREAM_STATE_DONE
23 } SoupBodyOutputStreamState;
24
25 struct _SoupBodyOutputStreamPrivate {
26         GOutputStream *base_stream;
27         char           buf[20];
28
29         SoupEncoding   encoding;
30         goffset        write_length;
31         goffset        written;
32         SoupBodyOutputStreamState chunked_state;
33         gboolean       eof;
34 };
35
36 enum {
37         PROP_0,
38
39         PROP_ENCODING,
40         PROP_CONTENT_LENGTH
41 };
42
43 static void soup_body_output_stream_pollable_init (GPollableOutputStreamInterface *pollable_interface, gpointer interface_data);
44
45 G_DEFINE_TYPE_WITH_CODE (SoupBodyOutputStream, soup_body_output_stream, G_TYPE_FILTER_OUTPUT_STREAM,
46                          G_ADD_PRIVATE (SoupBodyOutputStream)
47                          G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,
48                                                 soup_body_output_stream_pollable_init))
49
50
51 static void
52 soup_body_output_stream_init (SoupBodyOutputStream *stream)
53 {
54         stream->priv = soup_body_output_stream_get_instance_private (stream);
55 }
56
57 static void
58 soup_body_output_stream_constructed (GObject *object)
59 {
60         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (object);
61
62         bostream->priv->base_stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (bostream));
63 }
64
65 static void
66 soup_body_output_stream_set_property (GObject *object, guint prop_id,
67                                       const GValue *value, GParamSpec *pspec)
68 {
69         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (object);
70
71         switch (prop_id) {
72         case PROP_ENCODING:
73                 bostream->priv->encoding = g_value_get_enum (value);
74                 if (bostream->priv->encoding == SOUP_ENCODING_CHUNKED)
75                         bostream->priv->chunked_state = SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_SIZE;
76                 break;
77         case PROP_CONTENT_LENGTH:
78                 bostream->priv->write_length = g_value_get_uint64 (value);
79                 break;
80         default:
81                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
82                 break;
83         }
84 }
85
86 static void
87 soup_body_output_stream_get_property (GObject *object, guint prop_id,
88                                       GValue *value, GParamSpec *pspec)
89 {
90         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (object);
91
92         switch (prop_id) {
93         case PROP_ENCODING:
94                 g_value_set_enum (value, bostream->priv->encoding);
95                 break;
96         default:
97                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
98                 break;
99         }
100 }
101
102 static gssize
103 soup_body_output_stream_write_raw (SoupBodyOutputStream  *bostream,
104                                    const void            *buffer,
105                                    gsize                  count,
106                                    gboolean               blocking,
107                                    GCancellable          *cancellable,
108                                    GError               **error)
109 {
110         gssize nwrote, my_count;
111
112         /* If the caller tries to write too much to a Content-Length
113          * encoded stream, we truncate at the right point, but keep
114          * accepting additional data until they stop.
115          */
116         if (bostream->priv->write_length) {
117                 my_count = MIN (count, bostream->priv->write_length - bostream->priv->written);
118                 if (my_count == 0) {
119                         bostream->priv->eof = TRUE;
120                         return count;
121                 }
122         } else
123                 my_count = count;
124
125         nwrote = g_pollable_stream_write (bostream->priv->base_stream,
126                                           buffer, my_count,
127                                           blocking, cancellable, error);
128
129         if (nwrote > 0 && bostream->priv->write_length)
130                 bostream->priv->written += nwrote;
131
132         if (nwrote == my_count && my_count != count)
133                 nwrote = count;
134
135         return nwrote;
136 }
137
138 static gssize
139 soup_body_output_stream_write_chunked (SoupBodyOutputStream  *bostream,
140                                        const void            *buffer,
141                                        gsize                  count,
142                                        gboolean               blocking,
143                                        GCancellable          *cancellable,
144                                        GError               **error)
145 {
146         char *buf = bostream->priv->buf;
147         gssize nwrote, len;
148
149 again:
150         len = strlen (buf);
151         if (len) {
152                 nwrote = g_pollable_stream_write (bostream->priv->base_stream,
153                                                   buf, len, blocking,
154                                                   cancellable, error);
155                 if (nwrote < 0)
156                         return nwrote;
157                 memmove (buf, buf + nwrote, len + 1 - nwrote);
158                 goto again;
159         }
160
161         switch (bostream->priv->chunked_state) {
162         case SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_SIZE:
163                 g_snprintf (buf, sizeof (bostream->priv->buf),
164                             "%lx\r\n", (gulong)count);
165
166                 if (count > 0)
167                         bostream->priv->chunked_state = SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK;
168                 else
169                         bostream->priv->chunked_state = SOUP_BODY_OUTPUT_STREAM_STATE_TRAILERS;
170                 break;
171
172         case SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK:
173                 nwrote = g_pollable_stream_write (bostream->priv->base_stream,
174                                                   buffer, count, blocking,
175                                                   cancellable, error);
176                 if (nwrote < (gssize)count)
177                         return nwrote;
178
179                 bostream->priv->chunked_state = SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_END;
180                 break;
181
182         case SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_END:
183                 strncpy (buf, "\r\n", sizeof (bostream->priv->buf));
184                 bostream->priv->chunked_state = SOUP_BODY_OUTPUT_STREAM_STATE_DONE;
185                 break;
186
187         case SOUP_BODY_OUTPUT_STREAM_STATE_TRAILERS:
188                 strncpy (buf, "\r\n", sizeof (bostream->priv->buf));
189                 bostream->priv->chunked_state = SOUP_BODY_OUTPUT_STREAM_STATE_DONE;
190                 break;
191
192         case SOUP_BODY_OUTPUT_STREAM_STATE_DONE:
193                 bostream->priv->chunked_state = SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_SIZE;
194                 return count;
195         }
196
197         goto again;
198 }
199
200 static gssize
201 soup_body_output_stream_write_fn (GOutputStream  *stream,
202                                   const void     *buffer,
203                                   gsize           count,
204                                   GCancellable   *cancellable,
205                                   GError        **error)
206 {
207         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (stream);
208
209         if (bostream->priv->eof)
210                 return count;
211
212         switch (bostream->priv->encoding) {
213         case SOUP_ENCODING_CHUNKED:
214                 return soup_body_output_stream_write_chunked (bostream, buffer, count,
215                                                               TRUE, cancellable, error);
216
217         default:
218                 return soup_body_output_stream_write_raw (bostream, buffer, count,
219                                                           TRUE, cancellable, error);
220         }
221 }
222
223 static gboolean
224 soup_body_output_stream_close_fn (GOutputStream  *stream,
225                                   GCancellable   *cancellable,
226                                   GError        **error)
227 {
228         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (stream);
229
230         if (bostream->priv->encoding == SOUP_ENCODING_CHUNKED &&
231             bostream->priv->chunked_state == SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_SIZE) {
232                 if (soup_body_output_stream_write_chunked (bostream, NULL, 0, TRUE, cancellable, error) == -1)
233                         return FALSE;
234         }
235
236         return G_OUTPUT_STREAM_CLASS (soup_body_output_stream_parent_class)->close_fn (stream, cancellable, error);
237 }
238
239 static gboolean
240 soup_body_output_stream_is_writable (GPollableOutputStream *stream)
241 {
242         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (stream);
243
244         return bostream->priv->eof ||
245                 g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (bostream->priv->base_stream));
246 }
247
248 static gssize
249 soup_body_output_stream_write_nonblocking (GPollableOutputStream  *stream,
250                                            const void             *buffer,
251                                            gsize                   count,
252                                            GError                **error)
253 {
254         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (stream);
255
256         if (bostream->priv->eof)
257                 return count;
258
259         switch (bostream->priv->encoding) {
260         case SOUP_ENCODING_CHUNKED:
261                 return soup_body_output_stream_write_chunked (bostream, buffer, count,
262                                                               FALSE, NULL, error);
263
264         default:
265                 return soup_body_output_stream_write_raw (bostream, buffer, count,
266                                                           FALSE, NULL, error);
267         }
268 }
269
270 static GSource *
271 soup_body_output_stream_create_source (GPollableOutputStream *stream,
272                                        GCancellable *cancellable)
273 {
274         SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (stream);
275         GSource *base_source, *pollable_source;
276
277         if (bostream->priv->eof)
278                 base_source = g_timeout_source_new (0);
279         else
280                 base_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (bostream->priv->base_stream), cancellable);
281         g_source_set_dummy_callback (base_source);
282
283         pollable_source = g_pollable_source_new (G_OBJECT (stream));
284         g_source_add_child_source (pollable_source, base_source);
285         g_source_unref (base_source);
286
287         return pollable_source;
288 }
289
290 static void
291 soup_body_output_stream_class_init (SoupBodyOutputStreamClass *stream_class)
292 {
293         GObjectClass *object_class = G_OBJECT_CLASS (stream_class);
294         GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (stream_class);
295
296         object_class->constructed = soup_body_output_stream_constructed;
297         object_class->set_property = soup_body_output_stream_set_property;
298         object_class->get_property = soup_body_output_stream_get_property;
299
300         output_stream_class->write_fn = soup_body_output_stream_write_fn;
301         output_stream_class->close_fn = soup_body_output_stream_close_fn;
302
303         g_object_class_install_property (
304                 object_class, PROP_ENCODING,
305                 g_param_spec_enum ("encoding",
306                                    "Encoding",
307                                    "Message body encoding",
308                                    SOUP_TYPE_ENCODING,
309                                    SOUP_ENCODING_NONE,
310                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
311                                    G_PARAM_STATIC_STRINGS));
312         g_object_class_install_property (
313                 object_class, PROP_CONTENT_LENGTH,
314                 g_param_spec_uint64 ("content-length",
315                                      "Content-Length",
316                                      "Message body Content-Length",
317                                      0, G_MAXUINT64, 0,
318                                      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY |
319                                      G_PARAM_STATIC_STRINGS));
320 }
321
322 static void
323 soup_body_output_stream_pollable_init (GPollableOutputStreamInterface *pollable_interface,
324                                        gpointer interface_data)
325 {
326         pollable_interface->is_writable = soup_body_output_stream_is_writable;
327         pollable_interface->write_nonblocking = soup_body_output_stream_write_nonblocking;
328         pollable_interface->create_source = soup_body_output_stream_create_source;
329 }
330
331 GOutputStream *
332 soup_body_output_stream_new (GOutputStream *base_stream,
333                              SoupEncoding   encoding,
334                              goffset        content_length)
335 {
336         return g_object_new (SOUP_TYPE_BODY_OUTPUT_STREAM,
337                              "base-stream", base_stream,
338                              "close-base-stream", FALSE,
339                              "encoding", encoding,
340                              "content-length", content_length,
341                              NULL);
342 }