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
G_DEFINE_TYPE_WITH_CODE(SoupBodyOutputStream,soup_body_output_stream,G_TYPE_FILTER_OUTPUT_STREAM,G_ADD_PRIVATE (SoupBodyOutputStream)G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM,soup_body_output_stream_pollable_init))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
soup_body_output_stream_constructed(GObject * object)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
soup_body_output_stream_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)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
soup_body_output_stream_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)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
soup_body_output_stream_write_raw(SoupBodyOutputStream * bostream,const void * buffer,gsize count,gboolean blocking,GCancellable * cancellable,GError ** error)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
soup_body_output_stream_write_chunked(SoupBodyOutputStream * bostream,const void * buffer,gsize count,gboolean blocking,GCancellable * cancellable,GError ** error)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
soup_body_output_stream_write_fn(GOutputStream * stream,const void * buffer,gsize count,GCancellable * cancellable,GError ** error)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
soup_body_output_stream_close_fn(GOutputStream * stream,GCancellable * cancellable,GError ** error)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
soup_body_output_stream_is_writable(GPollableOutputStream * stream)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
soup_body_output_stream_write_nonblocking(GPollableOutputStream * stream,const void * buffer,gsize count,GError ** error)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 *
soup_body_output_stream_create_source(GPollableOutputStream * stream,GCancellable * cancellable)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
soup_body_output_stream_class_init(SoupBodyOutputStreamClass * stream_class)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
soup_body_output_stream_pollable_init(GPollableOutputStreamInterface * pollable_interface,gpointer interface_data)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 *
soup_body_output_stream_new(GOutputStream * base_stream,SoupEncoding encoding,goffset content_length)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 }
343