• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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