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