1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-multipart-input-stream.c
5 * Copyright (C) 2012 Collabora Ltd.
14 #include "soup-body-input-stream.h"
15 #include "soup-filter-input-stream.h"
16 #include "soup-enum-types.h"
17 #include "soup-message.h"
18 #include "soup-message-private.h"
19 #include "soup-multipart-input-stream.h"
21 #define RESPONSE_BLOCK_SIZE 8192
24 * SECTION:soup-multipart-input-stream
25 * @short_description: Multipart input handling stream
27 * This adds support for the multipart responses. For handling the
28 * multiple parts the user needs to wrap the #GInputStream obtained by
29 * sending the request with a #SoupMultipartInputStream and use
30 * soup_multipart_input_stream_next_part() before reading. Responses
31 * which are not wrapped will be treated like non-multipart responses.
33 * Note that although #SoupMultipartInputStream is a #GInputStream,
34 * you should not read directly from it, and the results are undefined
40 static void soup_multipart_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data);
42 G_DEFINE_TYPE_WITH_CODE (SoupMultipartInputStream, soup_multipart_input_stream, G_TYPE_FILTER_INPUT_STREAM,
43 G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM,
44 soup_multipart_input_stream_pollable_init))
52 struct _SoupMultipartInputStreamPrivate {
55 gboolean done_with_part;
58 SoupMessageHeaders *current_headers;
60 SoupFilterInputStream *base_stream;
65 goffset remaining_bytes;
69 soup_multipart_input_stream_dispose (GObject *object)
71 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object);
73 g_clear_object (&multipart->priv->msg);
74 g_clear_object (&multipart->priv->base_stream);
76 G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->dispose (object);
80 soup_multipart_input_stream_finalize (GObject *object)
82 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object);
84 g_free (multipart->priv->boundary);
86 if (multipart->priv->meta_buf)
87 g_clear_pointer (&multipart->priv->meta_buf, g_byte_array_unref);
89 G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->finalize (object);
93 soup_multipart_input_stream_set_property (GObject *object, guint prop_id,
94 const GValue *value, GParamSpec *pspec)
96 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object);
100 multipart->priv->msg = g_value_dup_object (value);
103 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
109 soup_multipart_input_stream_get_property (GObject *object, guint prop_id,
110 GValue *value, GParamSpec *pspec)
112 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object);
116 g_value_set_object (value, multipart->priv->msg);
119 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
125 soup_multipart_input_stream_read_real (GInputStream *stream,
129 GCancellable *cancellable,
132 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream);
133 SoupMultipartInputStreamPrivate *priv = multipart->priv;
134 gboolean got_boundary = FALSE;
138 g_return_val_if_fail (priv->boundary != NULL, -1);
140 /* If we have received a Content-Length, and are not yet close to the end of
141 * the part, let's not look for the boundary for now. This optimization is
142 * necessary for keeping CPU usage civil.
144 if (priv->remaining_bytes > priv->boundary_size) {
145 goffset bytes_to_read = MIN (count, priv->remaining_bytes - priv->boundary_size);
147 nread = g_pollable_stream_read (G_INPUT_STREAM (priv->base_stream),
148 buffer, bytes_to_read, blocking,
152 priv->remaining_bytes -= nread;
157 if (priv->done_with_part)
160 nread = soup_filter_input_stream_read_until (priv->base_stream, buffer, count,
161 priv->boundary, priv->boundary_size,
162 blocking, FALSE, &got_boundary,
171 priv->done_with_part = TRUE;
173 /* Ignore the newline that preceded the boundary. */
175 buf = ((guint8*)buffer);
176 if (!memcmp (buf, "\n", 1))
179 buf = ((guint8*)buffer) + nread - 2;
180 if (!memcmp (buf, "\r\n", 2))
182 else if (!memcmp (buf, "\n", 1))
190 soup_multipart_input_stream_read (GInputStream *stream,
193 GCancellable *cancellable,
196 return soup_multipart_input_stream_read_real (stream, buffer, count,
197 TRUE, cancellable, error);
201 soup_multipart_input_stream_init (SoupMultipartInputStream *multipart)
203 SoupMultipartInputStreamPrivate *priv;
204 priv = multipart->priv = G_TYPE_INSTANCE_GET_PRIVATE (multipart,
205 SOUP_TYPE_MULTIPART_INPUT_STREAM,
206 SoupMultipartInputStreamPrivate);
208 priv->meta_buf = g_byte_array_sized_new (RESPONSE_BLOCK_SIZE);
209 priv->done_with_part = FALSE;
213 soup_multipart_input_stream_constructed (GObject *object)
215 SoupMultipartInputStream *multipart;
216 SoupMultipartInputStreamPrivate *priv;
217 GInputStream *base_stream;
218 const char* boundary;
219 GHashTable *params = NULL;
221 multipart = SOUP_MULTIPART_INPUT_STREAM (object);
222 priv = multipart->priv;
224 base_stream = G_FILTER_INPUT_STREAM (multipart)->base_stream;
225 priv->base_stream = SOUP_FILTER_INPUT_STREAM (soup_filter_input_stream_new (base_stream));
227 soup_message_headers_get_content_type (priv->msg->response_headers,
230 boundary = g_hash_table_lookup (params, "boundary");
232 if (g_str_has_prefix (boundary, "--"))
233 priv->boundary = g_strdup (boundary);
235 priv->boundary = g_strdup_printf ("--%s", boundary);
237 priv->boundary_size = strlen (priv->boundary);
239 g_warning ("No boundary found in message tagged as multipart.");
242 g_hash_table_destroy (params);
244 if (G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->constructed)
245 G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->constructed (object);
249 soup_multipart_input_stream_is_readable (GPollableInputStream *stream)
251 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream);
252 SoupMultipartInputStreamPrivate *priv = multipart->priv;
254 return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (priv->base_stream));
258 soup_multipart_input_stream_read_nonblocking (GPollableInputStream *stream,
263 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream);
265 return soup_multipart_input_stream_read_real (G_INPUT_STREAM (multipart),
271 soup_multipart_input_stream_create_source (GPollableInputStream *stream,
272 GCancellable *cancellable)
274 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream);
275 SoupMultipartInputStreamPrivate *priv = multipart->priv;
276 GSource *base_source, *pollable_source;
278 base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (priv->base_stream), cancellable);
280 pollable_source = g_pollable_source_new_full (stream, base_source, cancellable);
281 g_source_unref (base_source);
283 return pollable_source;
287 soup_multipart_input_stream_class_init (SoupMultipartInputStreamClass *multipart_class)
289 GObjectClass *object_class = G_OBJECT_CLASS (multipart_class);
290 GInputStreamClass *input_stream_class =
291 G_INPUT_STREAM_CLASS (multipart_class);
293 g_type_class_add_private (multipart_class, sizeof (SoupMultipartInputStreamPrivate));
295 object_class->dispose = soup_multipart_input_stream_dispose;
296 object_class->finalize = soup_multipart_input_stream_finalize;
297 object_class->constructed = soup_multipart_input_stream_constructed;
298 object_class->set_property = soup_multipart_input_stream_set_property;
299 object_class->get_property = soup_multipart_input_stream_get_property;
301 input_stream_class->read_fn = soup_multipart_input_stream_read;
303 g_object_class_install_property (
304 object_class, PROP_MESSAGE,
305 g_param_spec_object ("message",
309 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
314 soup_multipart_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface,
315 gpointer interface_data)
317 pollable_interface->is_readable = soup_multipart_input_stream_is_readable;
318 pollable_interface->read_nonblocking = soup_multipart_input_stream_read_nonblocking;
319 pollable_interface->create_source = soup_multipart_input_stream_create_source;
323 soup_multipart_input_stream_parse_headers (SoupMultipartInputStream *multipart)
325 SoupMultipartInputStreamPrivate *priv = multipart->priv;
328 priv->current_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
330 /* The part lacks headers, but is there. */
331 if (!priv->meta_buf->len)
334 success = soup_headers_parse ((const char*) priv->meta_buf->data,
335 (int) priv->meta_buf->len,
336 priv->current_headers);
339 priv->remaining_bytes = soup_message_headers_get_content_length (priv->current_headers);
341 g_clear_pointer (&priv->current_headers, soup_message_headers_free);
343 g_byte_array_remove_range (priv->meta_buf, 0, priv->meta_buf->len);
347 soup_multipart_input_stream_read_headers (SoupMultipartInputStream *multipart,
348 GCancellable *cancellable,
351 SoupMultipartInputStreamPrivate *priv = multipart->priv;
352 guchar read_buf[RESPONSE_BLOCK_SIZE];
354 gboolean got_boundary = FALSE;
355 gboolean got_lf = FALSE;
358 g_return_val_if_fail (priv->boundary != NULL, TRUE);
360 g_clear_pointer (&priv->current_headers, soup_message_headers_free);
363 nread = soup_filter_input_stream_read_line (priv->base_stream, read_buf, sizeof (read_buf),
364 /* blocking */ TRUE, &got_lf, cancellable, error);
369 g_byte_array_append (priv->meta_buf, read_buf, nread);
371 /* Need to do this boundary check before checking for the line feed, since we
372 * may get the multipart end indicator without getting a new line.
375 !strncmp ((char *)priv->meta_buf->data,
377 priv->boundary_size)) {
380 /* Now check for possible multipart termination. */
381 buf = &read_buf[nread - 4];
382 if ((nread >= 4 && !memcmp (buf, "--\r\n", 4)) ||
383 (nread >= 3 && !memcmp (buf + 1, "--\n", 3)) ||
384 (nread >= 3 && !memcmp (buf + 2, "--", 2))) {
385 g_byte_array_set_size (priv->meta_buf, 0);
390 g_return_val_if_fail (got_lf, FALSE);
392 /* Discard pre-boundary lines. */
394 g_byte_array_set_size (priv->meta_buf, 0);
399 priv->meta_buf->len >= 2 &&
400 !strncmp ((char *)priv->meta_buf->data +
401 priv->meta_buf->len - 2,
404 else if (nread == 2 &&
405 priv->meta_buf->len >= 3 &&
406 !strncmp ((char *)priv->meta_buf->data +
407 priv->meta_buf->len - 3,
418 * soup_multipart_input_stream_new:
419 * @msg: the #SoupMessage the response is related to.
420 * @base_stream: the #GInputStream returned by sending the request.
422 * Creates a new #SoupMultipartInputStream that wraps the
423 * #GInputStream obtained by sending the #SoupRequest. Reads should
424 * not be done directly through this object, use the input streams
425 * returned by soup_multipart_input_stream_next_part() or its async
426 * counterpart instead.
428 * Returns: a new #SoupMultipartInputStream
432 SoupMultipartInputStream *
433 soup_multipart_input_stream_new (SoupMessage *msg,
434 GInputStream *base_stream)
436 return g_object_new (SOUP_TYPE_MULTIPART_INPUT_STREAM,
438 "base-stream", base_stream,
443 * soup_multipart_input_stream_next_part:
444 * @multipart: the #SoupMultipartInputStream
445 * @cancellable: a #GCancellable
448 * Obtains an input stream for the next part. When dealing with a
449 * multipart response the input stream needs to be wrapped in a
450 * #SoupMultipartInputStream and this function or its async
451 * counterpart need to be called to obtain the first part for
454 * After calling this function,
455 * soup_multipart_input_stream_get_headers() can be used to obtain the
456 * headers for the first part. A read of 0 bytes indicates the end of
457 * the part; a new call to this function should be done at that point,
458 * to obtain the next part.
460 * Return value: (transfer full): a new #GInputStream, or %NULL if
461 * there are no more parts
466 soup_multipart_input_stream_next_part (SoupMultipartInputStream *multipart,
467 GCancellable *cancellable,
470 if (!soup_multipart_input_stream_read_headers (multipart, cancellable, error))
473 soup_multipart_input_stream_parse_headers (multipart);
475 multipart->priv->done_with_part = FALSE;
477 return G_INPUT_STREAM (g_object_new (SOUP_TYPE_BODY_INPUT_STREAM,
478 "base-stream", G_INPUT_STREAM (multipart),
479 "close-base-stream", FALSE,
480 "encoding", SOUP_ENCODING_EOF,
486 soup_multipart_input_stream_next_part_thread (GTask *task,
489 GCancellable *cancellable)
491 SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object);
492 GError *error = NULL;
493 GInputStream *new_stream;
495 new_stream = soup_multipart_input_stream_next_part (multipart, cancellable, &error);
497 g_input_stream_clear_pending (G_INPUT_STREAM (multipart));
500 g_task_return_error (task, error);
502 g_task_return_pointer (task, new_stream, g_object_unref);
506 * soup_multipart_input_stream_next_part_async:
507 * @multipart: the #SoupMultipartInputStream.
508 * @io_priority: the I/O priority for the request.
509 * @cancellable: a #GCancellable.
510 * @callback: callback to call when request is satisfied.
511 * @data: data for @callback
513 * Obtains a #GInputStream for the next request. See
514 * soup_multipart_input_stream_next_part() for details on the
520 soup_multipart_input_stream_next_part_async (SoupMultipartInputStream *multipart,
522 GCancellable *cancellable,
523 GAsyncReadyCallback callback,
526 GInputStream *stream = G_INPUT_STREAM (multipart);
528 GError *error = NULL;
530 g_return_if_fail (SOUP_IS_MULTIPART_INPUT_STREAM (multipart));
532 task = g_task_new (multipart, cancellable, callback, data);
533 g_task_set_priority (task, io_priority);
535 if (!g_input_stream_set_pending (stream, &error)) {
536 g_task_return_error (task, error);
537 g_object_unref (task);
541 g_task_run_in_thread (task, soup_multipart_input_stream_next_part_thread);
542 g_object_unref (task);
546 * soup_multipart_input_stream_next_part_finish:
547 * @multipart: a #SoupMultipartInputStream.
548 * @result: a #GAsyncResult.
549 * @error: a #GError location to store any error, or NULL to ignore.
551 * Finishes an asynchronous request for the next part.
553 * Return value: (transfer full): a newly created #GInputStream for
554 * reading the next part or %NULL if there are no more parts.
559 soup_multipart_input_stream_next_part_finish (SoupMultipartInputStream *multipart,
560 GAsyncResult *result,
563 g_return_val_if_fail (g_task_is_valid (result, multipart), FALSE);
565 return g_task_propagate_pointer (G_TASK (result), error);
569 * soup_multipart_input_stream_get_headers:
570 * @multipart: a #SoupMultipartInputStream.
572 * Obtains the headers for the part currently being processed. Note
573 * that the #SoupMessageHeaders that are returned are owned by the
574 * #SoupMultipartInputStream and will be replaced when a call is made
575 * to soup_multipart_input_stream_next_part() or its async
576 * counterpart, so if keeping the headers is required, a copy must be
579 * Note that if a part had no headers at all an empty #SoupMessageHeaders
582 * Return value: (transfer none): a #SoupMessageHeaders containing the headers
583 * for the part currently being processed or %NULL if the headers failed to
589 soup_multipart_input_stream_get_headers (SoupMultipartInputStream *multipart)
591 return multipart->priv->current_headers;