/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * soup-content-sniffer-stream.c * * Copyright (C) 2010 Red Hat, Inc. */ #ifdef HAVE_CONFIG_H #include #endif #include #include "soup-content-sniffer-stream.h" #include "soup.h" enum { PROP_0, PROP_SNIFFER, PROP_MESSAGE, }; struct _SoupContentSnifferStreamPrivate { SoupContentSniffer *sniffer; SoupMessage *msg; guchar *buffer; gsize buffer_size, buffer_nread; gboolean sniffing; GError *error; char *sniffed_type; GHashTable *sniffed_params; }; static void soup_content_sniffer_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); G_DEFINE_TYPE_WITH_CODE (SoupContentSnifferStream, soup_content_sniffer_stream, G_TYPE_FILTER_INPUT_STREAM, G_ADD_PRIVATE (SoupContentSnifferStream) G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM, soup_content_sniffer_stream_pollable_init)) static void soup_content_sniffer_stream_finalize (GObject *object) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (object); g_clear_object (&sniffer->priv->sniffer); g_clear_object (&sniffer->priv->msg); g_free (sniffer->priv->buffer); g_clear_error (&sniffer->priv->error); g_free (sniffer->priv->sniffed_type); g_clear_pointer (&sniffer->priv->sniffed_params, g_hash_table_unref); G_OBJECT_CLASS (soup_content_sniffer_stream_parent_class)->finalize (object); } static void soup_content_sniffer_stream_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (object); switch (prop_id) { case PROP_SNIFFER: sniffer->priv->sniffer = g_value_dup_object (value); /* FIXME: supposed to wait until after got-headers for this */ sniffer->priv->buffer_size = soup_content_sniffer_get_buffer_size (sniffer->priv->sniffer); sniffer->priv->buffer = g_malloc (sniffer->priv->buffer_size); break; case PROP_MESSAGE: sniffer->priv->msg = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void soup_content_sniffer_stream_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (object); switch (prop_id) { case PROP_SNIFFER: g_value_set_object (value, sniffer->priv->sniffer); break; case PROP_MESSAGE: g_value_set_object (value, sniffer->priv->msg); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gssize read_and_sniff (GInputStream *stream, gboolean blocking, GCancellable *cancellable, GError **error) { SoupContentSnifferStreamPrivate *priv = SOUP_CONTENT_SNIFFER_STREAM (stream)->priv; gssize nread; GError *my_error = NULL; SoupBuffer *buf; do { nread = g_pollable_stream_read (G_FILTER_INPUT_STREAM (stream)->base_stream, priv->buffer + priv->buffer_nread, priv->buffer_size - priv->buffer_nread, blocking, cancellable, &my_error); if (nread <= 0) break; priv->buffer_nread += nread; } while (priv->buffer_nread < priv->buffer_size); /* If we got EAGAIN or cancellation before filling the buffer, * just return that right away. Likewise if we got any other * error without ever reading any data. Otherwise, save the * error to return after we're done sniffing. */ if (my_error) { if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) || g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_CANCELLED) || priv->buffer_nread == 0) { g_propagate_error (error, my_error); return -1; } else priv->error = my_error; } /* Sniff, then return the data */ buf = soup_buffer_new (SOUP_MEMORY_TEMPORARY, priv->buffer, priv->buffer_nread); priv->sniffed_type = soup_content_sniffer_sniff (priv->sniffer, priv->msg, buf, &priv->sniffed_params); soup_buffer_free (buf); priv->sniffing = FALSE; return priv->buffer_nread; } static gssize read_internal (GInputStream *stream, void *buffer, gsize count, gboolean blocking, GCancellable *cancellable, GError **error) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream); gssize nread; if (sniffer->priv->error) { g_propagate_error (error, sniffer->priv->error); sniffer->priv->error = NULL; return -1; } if (sniffer->priv->sniffing) { nread = read_and_sniff (stream, blocking, cancellable, error); if (nread <= 0) return nread; } if (sniffer->priv->buffer) { nread = MIN (count, sniffer->priv->buffer_nread); if (buffer) memcpy (buffer, sniffer->priv->buffer, nread); if (nread == sniffer->priv->buffer_nread) { g_free (sniffer->priv->buffer); sniffer->priv->buffer = NULL; } else { /* FIXME, inefficient */ memmove (sniffer->priv->buffer, sniffer->priv->buffer + nread, sniffer->priv->buffer_nread - nread); sniffer->priv->buffer_nread -= nread; } } else { nread = g_pollable_stream_read (G_FILTER_INPUT_STREAM (stream)->base_stream, buffer, count, blocking, cancellable, error); } return nread; } static gssize soup_content_sniffer_stream_read (GInputStream *stream, void *buffer, gsize count, GCancellable *cancellable, GError **error) { return read_internal (stream, buffer, count, TRUE, cancellable, error); } static gssize soup_content_sniffer_stream_skip (GInputStream *stream, gsize count, GCancellable *cancellable, GError **error) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream); gssize nskipped; if (sniffer->priv->sniffing) { /* Read into the internal buffer... */ nskipped = soup_content_sniffer_stream_read (stream, NULL, 0, cancellable, error); if (nskipped == -1) return -1; /* Now fall through */ } if (sniffer->priv->buffer) { nskipped = MIN (count, sniffer->priv->buffer_nread); if (nskipped == sniffer->priv->buffer_nread) { g_free (sniffer->priv->buffer); sniffer->priv->buffer = NULL; } else { /* FIXME */ memmove (sniffer->priv->buffer, sniffer->priv->buffer + nskipped, sniffer->priv->buffer_nread - nskipped); sniffer->priv->buffer_nread -= nskipped; } } else { nskipped = G_INPUT_STREAM_CLASS (soup_content_sniffer_stream_parent_class)-> skip (stream, count, cancellable, error); } return nskipped; } static gboolean soup_content_sniffer_stream_can_poll (GPollableInputStream *pollable) { GInputStream *base_stream = G_FILTER_INPUT_STREAM (pollable)->base_stream; return G_IS_POLLABLE_INPUT_STREAM (base_stream) && g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (base_stream)); } static gboolean soup_content_sniffer_stream_is_readable (GPollableInputStream *stream) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream); if (sniffer->priv->error || (!sniffer->priv->sniffing && sniffer->priv->buffer)) return TRUE; return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream)); } static gssize soup_content_sniffer_stream_read_nonblocking (GPollableInputStream *stream, void *buffer, gsize count, GError **error) { return read_internal (G_INPUT_STREAM (stream), buffer, count, FALSE, NULL, error); } static GSource * soup_content_sniffer_stream_create_source (GPollableInputStream *stream, GCancellable *cancellable) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream); GSource *base_source, *pollable_source; if (sniffer->priv->error || (!sniffer->priv->sniffing && sniffer->priv->buffer)) base_source = g_timeout_source_new (0); else base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream), cancellable); g_source_set_dummy_callback (base_source); pollable_source = g_pollable_source_new (G_OBJECT (stream)); g_source_add_child_source (pollable_source, base_source); g_source_unref (base_source); return pollable_source; } static void soup_content_sniffer_stream_init (SoupContentSnifferStream *sniffer) { sniffer->priv = soup_content_sniffer_stream_get_instance_private (sniffer); sniffer->priv->sniffing = TRUE; } static void soup_content_sniffer_stream_class_init (SoupContentSnifferStreamClass *sniffer_class) { GObjectClass *object_class = G_OBJECT_CLASS (sniffer_class); GInputStreamClass *input_stream_class = G_INPUT_STREAM_CLASS (sniffer_class); object_class->finalize = soup_content_sniffer_stream_finalize; object_class->set_property = soup_content_sniffer_stream_set_property; object_class->get_property = soup_content_sniffer_stream_get_property; input_stream_class->read_fn = soup_content_sniffer_stream_read; input_stream_class->skip = soup_content_sniffer_stream_skip; g_object_class_install_property ( object_class, PROP_SNIFFER, g_param_spec_object ("sniffer", "Sniffer", "The stream's SoupContentSniffer", SOUP_TYPE_CONTENT_SNIFFER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_MESSAGE, g_param_spec_object ("message", "Message", "The stream's SoupMessage", SOUP_TYPE_MESSAGE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void soup_content_sniffer_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data) { pollable_interface->can_poll = soup_content_sniffer_stream_can_poll; pollable_interface->is_readable = soup_content_sniffer_stream_is_readable; pollable_interface->read_nonblocking = soup_content_sniffer_stream_read_nonblocking; pollable_interface->create_source = soup_content_sniffer_stream_create_source; } gboolean soup_content_sniffer_stream_is_ready (SoupContentSnifferStream *sniffer, gboolean blocking, GCancellable *cancellable, GError **error) { if (!sniffer->priv->sniffing) return TRUE; return read_and_sniff (G_INPUT_STREAM (sniffer), blocking, cancellable, error) != -1; } const char * soup_content_sniffer_stream_sniff (SoupContentSnifferStream *sniffer, GHashTable **params) { if (params) *params = sniffer->priv->sniffed_params; return sniffer->priv->sniffed_type; }