/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* soup-converter-wrapper.c
*
* Copyright 2011 Red Hat, Inc.
*/
#include "config.h"
#include <string.h>
#include <glib/gi18n-lib.h>
#include "soup-converter-wrapper.h"
#include "soup.h"
/* SoupConverterWrapper is a GConverter that wraps another GConverter.
* Mostly it is transparent, but it implements four special fallbacks
* for Content-Encoding handling: (1) "deflate" can mean either raw
* deflate or zlib-encoded deflate, (2) the server may mistakenly
* claim that a response is encoded when actually it isn't, (3) the
* response may contain trailing junk after the end of the encoded
* portion that we want to ignore, (4) the response may be truncated
* at an arbitrary point rather than containing a complete compressed
* representation.
*
* If the wrapped conversion succeeds, then the wrapper will set the
* %SOUP_MESSAGE_CONTENT_DECODED flag on its message.
*/
enum {
PROP_0,
PROP_BASE_CONVERTER,
PROP_MESSAGE
};
struct _SoupConverterWrapperPrivate
{
GConverter *base_converter;
SoupMessage *msg;
gboolean try_deflate_fallback;
gboolean started;
gboolean discarding;
};
static void soup_converter_wrapper_iface_init (GConverterIface *iface);
G_DEFINE_TYPE_WITH_CODE (SoupConverterWrapper, soup_converter_wrapper, G_TYPE_OBJECT,
G_ADD_PRIVATE (SoupConverterWrapper)
G_IMPLEMENT_INTERFACE (G_TYPE_CONVERTER,
soup_converter_wrapper_iface_init))
static void
soup_converter_wrapper_init (SoupConverterWrapper *converter)
{
converter->priv = soup_converter_wrapper_get_instance_private (converter);
}
static void
soup_converter_wrapper_finalize (GObject *object)
{
SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (object)->priv;
g_clear_object (&priv->base_converter);
g_clear_object (&priv->msg);
G_OBJECT_CLASS (soup_converter_wrapper_parent_class)->finalize (object);
}
static void
soup_converter_wrapper_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (object)->priv;
switch (prop_id) {
case PROP_BASE_CONVERTER:
priv->base_converter = g_value_dup_object (value);
if (G_IS_ZLIB_DECOMPRESSOR (priv->base_converter)) {
GZlibCompressorFormat format;
g_object_get (G_OBJECT (priv->base_converter),
"format", &format,
NULL);
if (format == G_ZLIB_COMPRESSOR_FORMAT_ZLIB)
priv->try_deflate_fallback = TRUE;
}
break;
case PROP_MESSAGE:
priv->msg = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
soup_converter_wrapper_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (object)->priv;
switch (prop_id) {
case PROP_BASE_CONVERTER:
g_value_set_object (value, priv->base_converter);
break;
case PROP_MESSAGE:
g_value_set_object (value, priv->msg);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
soup_converter_wrapper_class_init (SoupConverterWrapperClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = soup_converter_wrapper_finalize;
gobject_class->get_property = soup_converter_wrapper_get_property;
gobject_class->set_property = soup_converter_wrapper_set_property;
g_object_class_install_property (gobject_class,
PROP_BASE_CONVERTER,
g_param_spec_object ("base-converter",
"Base GConverter",
"GConverter to wrap",
G_TYPE_CONVERTER,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_MESSAGE,
g_param_spec_object ("message",
"Message",
"Associated SoupMessage",
SOUP_TYPE_MESSAGE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}
GConverter *
soup_converter_wrapper_new (GConverter *base_converter,
SoupMessage *msg)
{
return g_object_new (SOUP_TYPE_CONVERTER_WRAPPER,
"base-converter", base_converter,
"message", msg,
NULL);
}
static void
soup_converter_wrapper_reset (GConverter *converter)
{
SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (converter)->priv;
if (priv->base_converter)
g_converter_reset (priv->base_converter);
}
static GConverterResult
soup_converter_wrapper_fallback_convert (GConverter *converter,
const void *inbuf,
gsize inbuf_size,
void *outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (converter)->priv;
if (outbuf_size == 0) {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NO_SPACE,
_("Output buffer is too small"));
return G_CONVERTER_ERROR;
}
if (priv->discarding) {
*bytes_read = inbuf_size;
*bytes_written = 0;
} else if (outbuf_size >= inbuf_size) {
memcpy (outbuf, inbuf, inbuf_size);
*bytes_read = *bytes_written = inbuf_size;
} else {
memcpy (outbuf, inbuf, outbuf_size);
*bytes_read = *bytes_written = outbuf_size;
}
if (*bytes_read < inbuf_size)
return G_CONVERTER_CONVERTED;
if (flags & G_CONVERTER_INPUT_AT_END)
return G_CONVERTER_FINISHED;
else if (flags & G_CONVERTER_FLUSH)
return G_CONVERTER_FLUSHED;
else if (inbuf_size)
return G_CONVERTER_CONVERTED;
else {
/* Force it to either read more input or
* try again with G_CONVERTER_INPUT_AT_END.
*/
g_set_error_literal (error, G_IO_ERROR,
G_IO_ERROR_PARTIAL_INPUT,
"");
return G_CONVERTER_ERROR;
}
}
static GConverterResult
soup_converter_wrapper_real_convert (GConverter *converter,
const void *inbuf,
gsize inbuf_size,
void *outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (converter)->priv;
GConverterResult result;
GError *my_error = NULL;
try_again:
result = g_converter_convert (priv->base_converter,
inbuf, inbuf_size,
outbuf, outbuf_size,
flags, bytes_read, bytes_written,
&my_error);
if (result != G_CONVERTER_ERROR) {
if (!priv->started) {
SoupMessageFlags message_flags = soup_message_get_flags (priv->msg);
soup_message_set_flags (priv->msg, message_flags | SOUP_MESSAGE_CONTENT_DECODED);
priv->started = TRUE;
}
if (result == G_CONVERTER_FINISHED &&
!(flags & G_CONVERTER_INPUT_AT_END)) {
/* We need to keep reading (and discarding)
* input to the end of the message body.
*/
g_clear_object (&priv->base_converter);
priv->discarding = TRUE;
if (*bytes_written)
return G_CONVERTER_CONVERTED;
else {
g_set_error_literal (error, G_IO_ERROR,
G_IO_ERROR_PARTIAL_INPUT,
"");
return G_CONVERTER_ERROR;
}
}
return result;
}
if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT) &&
inbuf_size == 0 && (flags & G_CONVERTER_INPUT_AT_END)) {
/* Server claimed compression but there was no message body. */
g_error_free (my_error);
*bytes_written = 0;
return G_CONVERTER_FINISHED;
}
if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA) ||
priv->started) {
g_propagate_error (error, my_error);
return result;
}
g_clear_error (&my_error);
/* Deflate hack: some servers (especially Apache with
* mod_deflate) return raw compressed data without the zlib
* headers when the client claims to support deflate.
*/
if (priv->try_deflate_fallback) {
priv->try_deflate_fallback = FALSE;
g_object_unref (priv->base_converter);
priv->base_converter = (GConverter *)
g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW);
goto try_again;
}
/* Passthrough hack: some servers mistakenly claim to be
* sending encoded data when in fact they aren't, so fall
* back to just not decoding.
*/
g_clear_object (&priv->base_converter);
return soup_converter_wrapper_fallback_convert (converter,
inbuf, inbuf_size,
outbuf, outbuf_size,
flags, bytes_read,
bytes_written, error);
}
static GConverterResult
soup_converter_wrapper_convert (GConverter *converter,
const void *inbuf,
gsize inbuf_size,
void *outbuf,
gsize outbuf_size,
GConverterFlags flags,
gsize *bytes_read,
gsize *bytes_written,
GError **error)
{
SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (converter)->priv;
if (priv->base_converter) {
return soup_converter_wrapper_real_convert (converter,
inbuf, inbuf_size,
outbuf, outbuf_size,
flags, bytes_read,
bytes_written, error);
} else {
return soup_converter_wrapper_fallback_convert (converter,
inbuf, inbuf_size,
outbuf, outbuf_size,
flags, bytes_read,
bytes_written, error);
}
}
static void
soup_converter_wrapper_iface_init (GConverterIface *iface)
{
iface->convert = soup_converter_wrapper_convert;
iface->reset = soup_converter_wrapper_reset;
}