/* -*- 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 <config.h>
#endif
#include <string.h>
#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;
}