summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Winship <danw@gnome.org>2008-03-15 09:52:35 -0400
committerDan Winship <danw@gnome.org>2009-12-16 15:40:29 +0100
commit35e78b4ded68e5d6550d51ec161ff0034fd3736b (patch)
treee1f159185ed8ac8fcc8ceeef096b0b280f36c65c
parent5a6e1e873e13e1bc77298b501ff6f7a66fadcb63 (diff)
downloadlibsoup-35e78b4ded68e5d6550d51ec161ff0034fd3736b.tar.gz
Content-Encoding support
Adds SoupContentDecoder, which provides support for decoding "gzip" Content-Encoding. For now other types are not supported and can't be added. The SoupCoding interface is private because it will eventually be replaced with something GConverter-based. https://bugzilla.gnome.org/show_bug.cgi?id=522772
-rw-r--r--.gitignore18
-rw-r--r--libsoup/Makefile.am6
-rw-r--r--libsoup/soup-coding-gzip.c142
-rw-r--r--libsoup/soup-coding-gzip.h33
-rw-r--r--libsoup/soup-coding.c258
-rw-r--r--libsoup/soup-coding.h92
-rw-r--r--libsoup/soup-content-decoder.c165
-rw-r--r--libsoup/soup-content-decoder.h44
-rw-r--r--libsoup/soup-message-io.c43
-rw-r--r--libsoup/soup-message-private.h1
-rw-r--r--libsoup/soup-message.c12
-rw-r--r--libsoup/soup-message.h3
-rw-r--r--libsoup/soup.h1
-rw-r--r--tests/Makefile.am3
-rw-r--r--tests/coding-test.c154
-rw-r--r--tests/get.c7
-rw-r--r--tests/resources/mbox.gzbin0 -> 310 bytes
17 files changed, 962 insertions, 20 deletions
diff --git a/.gitignore b/.gitignore
index 9377d34d..f5973752 100644
--- a/.gitignore
+++ b/.gitignore
@@ -53,30 +53,14 @@ missing
python/COPYING
python/m4
stamp-h1
-tests/auth-test
-tests/chunk-test
-tests/context-test
-tests/continue-test
+tests/*-test
tests/date
tests/dns
-tests/forms-test
tests/get
tests/getbug
tests/header-parsing
tests/httpd.conf
-tests/misc-test
-tests/ntlm-test
-tests/proxy-test
tests/pull-api
-tests/range-test
-tests/redirect-test
-tests/server-auth-test
tests/simple-httpd
tests/simple-proxy
-tests/sniffing-test
-tests/ssl-test
-tests/streaming-test
-tests/timeout-test
tests/uri-parsing
-tests/xmlrpc-server-test
-tests/xmlrpc-test
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index c25b534c..53fae83f 100644
--- a/libsoup/Makefile.am
+++ b/libsoup/Makefile.am
@@ -56,6 +56,7 @@ soup_headers = \
soup-auth-domain.h \
soup-auth-domain-basic.h \
soup-auth-domain-digest.h \
+ soup-content-decoder.h \
soup-content-sniffer.h \
soup-cookie.h \
soup-cookie-jar.h \
@@ -120,8 +121,13 @@ libsoup_2_4_la_SOURCES = \
soup-auth-manager.c \
soup-auth-manager-ntlm.h \
soup-auth-manager-ntlm.c \
+ soup-coding.h \
+ soup-coding.c \
+ soup-coding-gzip.h \
+ soup-coding-gzip.c \
soup-connection.h \
soup-connection.c \
+ soup-content-decoder.c \
soup-content-sniffer.c \
soup-cookie.c \
soup-cookie-jar.c \
diff --git a/libsoup/soup-coding-gzip.c b/libsoup/soup-coding-gzip.c
new file mode 100644
index 00000000..b1731d57
--- /dev/null
+++ b/libsoup/soup-coding-gzip.c
@@ -0,0 +1,142 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-coding-gzip.c: "gzip" coding
+ *
+ * Copyright (C) 2005 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-coding-gzip.h"
+
+#include <zlib.h>
+
+typedef struct {
+ z_stream stream;
+
+} SoupCodingGzipPrivate;
+#define SOUP_CODING_GZIP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CODING_GZIP, SoupCodingGzipPrivate))
+
+G_DEFINE_TYPE (SoupCodingGzip, soup_coding_gzip, SOUP_TYPE_CODING)
+
+static void constructed (GObject *object);
+static void finalize (GObject *object);
+static SoupCodingStatus apply_into (SoupCoding *coding,
+ gconstpointer input, gsize input_length,
+ gsize *input_used,
+ gpointer output, gsize output_length,
+ gsize *output_used,
+ gboolean done, GError **error);
+
+static void
+soup_coding_gzip_init (SoupCodingGzip *gzip)
+{
+ SoupCodingGzipPrivate *priv = SOUP_CODING_GZIP_GET_PRIVATE (gzip);
+
+ priv->stream.zalloc = Z_NULL;
+ priv->stream.zfree = Z_NULL;
+ priv->stream.opaque = Z_NULL;
+}
+
+static void
+soup_coding_gzip_class_init (SoupCodingGzipClass *gzip_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (gzip_class);
+ SoupCodingClass *coding_class = SOUP_CODING_CLASS (gzip_class);
+
+ g_type_class_add_private (gzip_class, sizeof (SoupCodingGzipPrivate));
+
+ coding_class->name = "gzip";
+
+ object_class->constructed = constructed;
+ object_class->finalize = finalize;
+
+ coding_class->apply_into = apply_into;
+}
+
+static void
+constructed (GObject *object)
+{
+ SoupCodingGzipPrivate *priv = SOUP_CODING_GZIP_GET_PRIVATE (object);
+
+ /* All of these values are the defaults according to the zlib
+ * documentation. "16" is a magic number that means gzip
+ * instead of zlib.
+ */
+ if (SOUP_CODING (object)->direction == SOUP_CODING_ENCODE) {
+ deflateInit2 (&priv->stream, Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED, MAX_WBITS | 16, 8,
+ Z_DEFAULT_STRATEGY);
+ } else
+ inflateInit2 (&priv->stream, MAX_WBITS | 16);
+}
+
+static void
+finalize (GObject *object)
+{
+ SoupCodingGzipPrivate *priv = SOUP_CODING_GZIP_GET_PRIVATE (object);
+
+ if (SOUP_CODING (object)->direction == SOUP_CODING_ENCODE)
+ deflateEnd (&priv->stream);
+ else
+ inflateEnd (&priv->stream);
+
+ G_OBJECT_CLASS (soup_coding_gzip_parent_class)->finalize (object);
+}
+
+static SoupCodingStatus
+apply_into (SoupCoding *coding,
+ gconstpointer input, gsize input_length, gsize *input_used,
+ gpointer output, gsize output_length, gsize *output_used,
+ gboolean done, GError **error)
+{
+ SoupCodingGzipPrivate *priv = SOUP_CODING_GZIP_GET_PRIVATE (coding);
+ int ret;
+
+ priv->stream.avail_in = input_length;
+ priv->stream.next_in = (gpointer)input;
+ priv->stream.total_in = 0;
+
+ priv->stream.avail_out = output_length;
+ priv->stream.next_out = output;
+ priv->stream.total_out = 0;
+
+ if (coding->direction == SOUP_CODING_ENCODE)
+ ret = deflate (&priv->stream, done ? Z_FINISH : Z_NO_FLUSH);
+ else
+ ret = inflate (&priv->stream, Z_SYNC_FLUSH);
+
+ *input_used = priv->stream.total_in;
+ *output_used = priv->stream.total_out;
+
+ switch (ret) {
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR:
+ case Z_STREAM_ERROR:
+ g_set_error_literal (error, SOUP_CODING_ERROR,
+ SOUP_CODING_ERROR_DATA_ERROR,
+ priv->stream.msg ? priv->stream.msg : "Bad data");
+ return SOUP_CODING_STATUS_ERROR;
+
+ case Z_BUF_ERROR:
+ case Z_MEM_ERROR:
+ g_set_error_literal (error, SOUP_CODING_ERROR,
+ SOUP_CODING_ERROR_INTERNAL_ERROR,
+ priv->stream.msg ? priv->stream.msg : "Internal error");
+ return SOUP_CODING_STATUS_ERROR;
+
+ case Z_STREAM_END:
+ return SOUP_CODING_STATUS_COMPLETE;
+
+ case Z_OK:
+ default:
+ if (*output_used == output_length &&
+ *input_used < input_length)
+ return SOUP_CODING_STATUS_NEED_SPACE;
+ else
+ return SOUP_CODING_STATUS_OK;
+ }
+}
diff --git a/libsoup/soup-coding-gzip.h b/libsoup/soup-coding-gzip.h
new file mode 100644
index 00000000..abdca374
--- /dev/null
+++ b/libsoup/soup-coding-gzip.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifndef SOUP_CODING_GZIP_H
+#define SOUP_CODING_GZIP_H 1
+
+#include "soup-coding.h"
+
+#define SOUP_TYPE_CODING_GZIP (soup_coding_gzip_get_type ())
+#define SOUP_CODING_GZIP(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_CODING_GZIP, SoupCodingGzip))
+#define SOUP_CODING_GZIP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CODING_GZIP, SoupCodingGzipClass))
+#define SOUP_IS_CODING_GZIP(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_CODING_GZIP))
+#define SOUP_IS_CODING_GZIP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CODING_GZIP))
+#define SOUP_CODING_GZIP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CODING_GZIP, SoupCodingGzipClass))
+
+typedef struct {
+ SoupCoding parent;
+
+} SoupCodingGzip;
+
+typedef struct {
+ SoupCodingClass parent_class;
+
+} SoupCodingGzipClass;
+
+GType soup_coding_gzip_get_type (void);
+
+SoupCoding *soup_coding_gzip_new (SoupCodingDirection direction);
+
+#endif /* SOUP_CODING_GZIP_H */
diff --git a/libsoup/soup-coding.c b/libsoup/soup-coding.c
new file mode 100644
index 00000000..74c48268
--- /dev/null
+++ b/libsoup/soup-coding.c
@@ -0,0 +1,258 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-coding.c: Data encoding/decoding class
+ *
+ * Copyright (C) 2005 Novell, Inc.
+ * Copyright (C) 2008 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "soup-coding.h"
+#include "soup-enum-types.h"
+#include "soup-session-feature.h"
+
+static void soup_coding_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SoupCoding, soup_coding, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+ soup_coding_session_feature_init))
+
+enum {
+ PROP_0,
+
+ PROP_DIRECTION,
+
+ LAST_PROP
+};
+
+static void set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec);
+static void get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec);
+
+static SoupBuffer *apply (SoupCoding *coding,
+ gconstpointer input, gsize input_length,
+ gboolean done, GError **error);
+
+static void
+soup_coding_class_init (SoupCodingClass *coding_class)
+{
+ GObjectClass *object_class = (GObjectClass *)coding_class;
+
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+
+ coding_class->apply = apply;
+
+ /* properties */
+ g_object_class_install_property (
+ object_class, PROP_DIRECTION,
+#if 0
+ g_param_spec_enum (SOUP_CODING_DIRECTION,
+#else
+ g_param_spec_uint (SOUP_CODING_DIRECTION,
+#endif
+ "Direction",
+ "Whether to encode or decode",
+ 0, 2,
+ SOUP_CODING_ENCODE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+soup_coding_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+ gpointer interface_data)
+{
+ ;
+}
+
+static void
+soup_coding_init (SoupCoding *coding)
+{
+ ;
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupCoding *coding = SOUP_CODING (object);
+
+ switch (prop_id) {
+ case PROP_DIRECTION:
+#if 0
+ coding->direction = g_value_get_enum (value);
+#else
+ coding->direction = g_value_get_uint (value);
+#endif
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupCoding *coding = SOUP_CODING (object);
+
+ switch (prop_id) {
+ case PROP_DIRECTION:
+#if 0
+ g_value_set_enum (value, coding->direction);
+#else
+ g_value_set_uint (value, coding->direction);
+#endif
+ break;
+ default:
+ break;
+ }
+}
+
+static SoupBuffer *
+apply (SoupCoding *coding,
+ gconstpointer input, gsize input_length,
+ gboolean done, GError **error)
+{
+ gsize outbuf_length, outbuf_used, outbuf_cur, input_used, input_cur;
+ char *outbuf;
+ SoupCodingStatus status;
+
+ if (coding->direction == SOUP_CODING_ENCODE)
+ outbuf_length = MAX (input_length / 2, 1024);
+ else
+ outbuf_length = MAX (input_length * 2, 1024);
+ outbuf = g_malloc (outbuf_length);
+ outbuf_cur = input_cur = 0;
+
+ do {
+ status = soup_coding_apply_into (
+ coding,
+ (guchar *)input + input_cur, input_length - input_cur,
+ &input_used,
+ outbuf + outbuf_cur, outbuf_length - outbuf_cur,
+ &outbuf_used,
+ done, error);
+ input_cur += input_used;
+ outbuf_cur += outbuf_used;
+
+ switch (status) {
+ case SOUP_CODING_STATUS_OK:
+ case SOUP_CODING_STATUS_COMPLETE:
+ break;
+
+ case SOUP_CODING_STATUS_NEED_SPACE:
+ outbuf_length *= 2;
+ outbuf = g_realloc (outbuf, outbuf_length);
+ break;
+
+ case SOUP_CODING_STATUS_ERROR:
+ default:
+ g_free (outbuf);
+ return NULL;
+ }
+ } while (input_cur < input_length ||
+ (done && status != SOUP_CODING_STATUS_COMPLETE));
+
+ if (outbuf_cur)
+ return soup_buffer_new (SOUP_MEMORY_TAKE, outbuf, outbuf_cur);
+ else {
+ g_free (outbuf);
+ return NULL;
+ }
+}
+
+/**
+ * soup_coding_apply:
+ * @coding: a #SoupCoding
+ * @input: input data
+ * @input_length: length of @input
+ * @done: %TRUE if this is the last piece of data to encode/decode
+ * @error: error pointer
+ *
+ * Applies @coding to @input_length bytes of data from @input, and
+ * returns a new #SoupBuffer containing the encoded/decoded data. If
+ * @done is %FALSE, the encoder may buffer some or all of the data in
+ * @input rather than outputting it right away. If @done is %TRUE, the
+ * encoder will flush any buffered data, and (if possible) verify that
+ * the input has reached the end of the stream.
+ *
+ * Return value: a #SoupBuffer containing the encoded/decoded data, or
+ * %NULL if no data can be returned at this point, or if an error
+ * occurred. (If you pass %NULL for @error, there is no way to
+ * distinguish the latter two cases).
+ **/
+SoupBuffer *
+soup_coding_apply (SoupCoding *coding,
+ gconstpointer input, gsize input_length,
+ gboolean done, GError **error)
+{
+ g_return_val_if_fail (SOUP_IS_CODING (coding), NULL);
+
+ return SOUP_CODING_GET_CLASS (coding)->apply (
+ coding, input, input_length, done, error);
+}
+
+/**
+ * SoupCodingStatus:
+ * @SOUP_CODING_STATUS_OK: Success
+ * @SOUP_CODING_STATUS_ERROR: An error occurred
+ * @SOUP_CODING_STATUS_NEED_SPACE: Output buffer was too small to
+ * output any data.
+ * @SOUP_CODING_STATUS_COMPLETE: The stream end has been reached and
+ * the output buffer contains the last bytes of encoded/decoded data.
+ *
+ * The result from a call to soup_coding_apply_into().
+ **/
+
+/**
+ * soup_coding_apply_into:
+ * @coding: a #SoupCoding
+ * @input: input data
+ * @input_length: length of @input
+ * @input_used: on return, contains the number of bytes of @input that
+ * were encoded/decoded.
+ * @output: output buffer
+ * @output_length: length of @output
+ * @output_used: on return, contains the number of bytes of @output that
+ * were filled with encoded/decoded data.
+ * @done: %TRUE if this is the last piece of data to encode/decode
+ * @error: error pointer
+ *
+ * Applies @coding to @input_length bytes of data from @input, and
+ * outputs between %0 and @output_length encoded/decoded bytes into
+ * @output. @input and @output may not overlap.
+ *
+ * Return value: the status; %SOUP_CODING_STATUS_OK on intermediate
+ * success, %SOUP_CODING_STATUS_COMPLETE if the stream has been fully
+ * encoded/decoded, %SOUP_CODING_STATUS_NEED_SPACE if a larger
+ * @output_length is required to make progress, or
+ * %SOUP_CODING_STATUS_ERROR on error (in which case @error will be
+ * set).
+ **/
+SoupCodingStatus
+soup_coding_apply_into (SoupCoding *coding,
+ gconstpointer input, gsize input_length, gsize *input_used,
+ gpointer output, gsize output_length, gsize *output_used,
+ gboolean done, GError **error)
+{
+ g_return_val_if_fail (SOUP_IS_CODING (coding), 0);
+
+ return SOUP_CODING_GET_CLASS (coding)->apply_into (
+ coding, input, input_length, input_used,
+ output, output_length, output_used,
+ done, error);
+}
+
+GQuark
+soup_coding_error_quark (void)
+{
+ static GQuark error;
+ if (!error)
+ error = g_quark_from_static_string ("soup_coding_error_quark");
+ return error;
+}
diff --git a/libsoup/soup-coding.h b/libsoup/soup-coding.h
new file mode 100644
index 00000000..a00d0140
--- /dev/null
+++ b/libsoup/soup-coding.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005, Novell, Inc.
+ * Copyright (C) 2008, Red Hat, Inc.
+ */
+
+#ifndef SOUP_CODING_H
+#define SOUP_CODING_H 1
+
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-message-body.h>
+
+#define SOUP_TYPE_CODING (soup_coding_get_type ())
+#define SOUP_CODING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_CODING, SoupCoding))
+#define SOUP_CODING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CODING, SoupCodingClass))
+#define SOUP_IS_CODING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_CODING))
+#define SOUP_IS_CODING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_CODING))
+#define SOUP_CODING_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CODING, SoupCodingClass))
+
+typedef enum {
+ SOUP_CODING_ENCODE,
+ SOUP_CODING_DECODE
+} SoupCodingDirection;
+
+typedef enum {
+ SOUP_CODING_STATUS_OK,
+ SOUP_CODING_STATUS_ERROR,
+ SOUP_CODING_STATUS_NEED_SPACE,
+ SOUP_CODING_STATUS_COMPLETE,
+} SoupCodingStatus;
+
+typedef struct {
+ GObject parent;
+
+ SoupCodingDirection direction;
+} SoupCoding;
+
+typedef struct {
+ GObjectClass parent_class;
+
+ const char *name;
+
+ SoupBuffer * (*apply) (SoupCoding *coding,
+ gconstpointer input,
+ gsize input_length,
+ gboolean done,
+ GError **error);
+ SoupCodingStatus (*apply_into) (SoupCoding *coding,
+ gconstpointer input,
+ gsize input_length,
+ gsize *input_used,
+ gpointer output,
+ gsize output_length,
+ gsize *output_used,
+ gboolean done,
+ GError **error);
+
+ /* Padding for future expansion */
+ void (*_libsoup_reserved1) (void);
+ void (*_libsoup_reserved2) (void);
+ void (*_libsoup_reserved3) (void);
+ void (*_libsoup_reserved4) (void);
+} SoupCodingClass;
+
+#define SOUP_CODING_DIRECTION "direction"
+
+GType soup_coding_get_type (void);
+
+SoupBuffer *soup_coding_apply (SoupCoding *coding,
+ gconstpointer input,
+ gsize input_length,
+ gboolean done,
+ GError **error);
+SoupCodingStatus soup_coding_apply_into (SoupCoding *coding,
+ gconstpointer input,
+ gsize input_length,
+ gsize *input_used,
+ gpointer output,
+ gsize output_length,
+ gsize *output_used,
+ gboolean done,
+ GError **error);
+
+#define SOUP_CODING_ERROR soup_coding_error_quark()
+GQuark soup_coding_error_quark (void);
+
+typedef enum {
+ SOUP_CODING_ERROR_DATA_ERROR,
+ SOUP_CODING_ERROR_INTERNAL_ERROR
+} SoupCodingError;
+
+#endif /* SOUP_CODING_H */
diff --git a/libsoup/soup-content-decoder.c b/libsoup/soup-content-decoder.c
new file mode 100644
index 00000000..5ebcff63
--- /dev/null
+++ b/libsoup/soup-content-decoder.c
@@ -0,0 +1,165 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-content-decoder.c
+ *
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <gio/gio.h>
+
+#include "soup-content-decoder.h"
+#include "soup-coding-gzip.h"
+#include "soup-enum-types.h"
+#include "soup-message.h"
+#include "soup-message-private.h"
+#include "soup-session-feature.h"
+#include "soup-uri.h"
+
+/**
+ * SECTION:soup-content-decoder
+ * @short_description: Content-Encoding handler
+ *
+ * #SoupContentDecoder handles the "Accept-Encoding" header on
+ * outgoing messages, and the "Content-Encoding" header on incoming
+ * ones. If you add it to a session with soup_session_add_feature() or
+ * soup_session_add_feature_by_type(), the session will automatically
+ * use Content-Encoding as appropriate.
+ *
+ * (Note that currently there is no way to (automatically) use
+ * Content-Encoding when sending a request body, or to pick specific
+ * encoding types to support.)
+ *
+ * If #SoupContentDecoder successfully decodes the Content-Encoding,
+ * it will set the %SOUP_MESSAGE_CONTENT_DECODED flag on the message,
+ * and the message body and the chunks in the #SoupMessage::got_chunk
+ * signals will contain the decoded data; however, the message headers
+ * will be unchanged (and so "Content-Encoding" will still be present,
+ * "Content-Length" will describe the original encoded length, etc).
+ *
+ * If "Content-Encoding" contains any encoding types that
+ * #SoupContentDecoder doesn't recognize, then none of the encodings
+ * will be decoded (and the %SOUP_MESSAGE_CONTENT_DECODED flag will
+ * not be set).
+ *
+ * Since: 2.28.2
+ **/
+
+struct _SoupContentDecoderPrivate {
+ GHashTable *codings;
+};
+
+static void soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+
+static void request_queued (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg);
+static void request_unqueued (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg);
+
+G_DEFINE_TYPE_WITH_CODE (SoupContentDecoder, soup_content_decoder, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
+ soup_content_decoder_session_feature_init))
+
+/* This is constant for now */
+#define ACCEPT_ENCODING_HEADER "gzip"
+
+static void
+soup_content_decoder_init (SoupContentDecoder *decoder)
+{
+ decoder->priv = G_TYPE_INSTANCE_GET_PRIVATE (decoder,
+ SOUP_TYPE_CONTENT_DECODER,
+ SoupContentDecoderPrivate);
+
+ decoder->priv->codings = g_hash_table_new (g_str_hash, g_str_equal);
+ /* Hardcoded for now */
+ g_hash_table_insert (decoder->priv->codings, "gzip",
+ GSIZE_TO_POINTER (SOUP_TYPE_CODING_GZIP));
+}
+
+static void
+soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class)
+{
+ g_type_class_add_private (decoder_class, sizeof (SoupContentDecoderPrivate));
+}
+
+static void
+soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface,
+ gpointer interface_data)
+{
+ feature_interface->request_queued = request_queued;
+ feature_interface->request_unqueued = request_unqueued;
+}
+
+static void
+soup_content_decoder_got_headers_cb (SoupMessage *msg, SoupContentDecoder *decoder)
+{
+ SoupMessagePrivate *msgpriv = SOUP_MESSAGE_GET_PRIVATE (msg);
+ const char *header;
+ GSList *encodings, *e;
+ GType coding_type;
+ SoupCoding *coding;
+
+ header = soup_message_headers_get_list (msg->response_headers,
+ "Content-Encoding");
+ if (!header)
+ return;
+
+ /* OK, really, no one is ever going to use more than one
+ * encoding, but we'll be robust.
+ */
+ encodings = soup_header_parse_list (header);
+ if (!encodings)
+ return;
+
+ for (e = encodings; e; e = e->next) {
+ if (!g_hash_table_lookup (decoder->priv->codings, e->data)) {
+ soup_header_free_list (encodings);
+ return;
+ }
+ }
+
+ msgpriv->decoders = NULL;
+ for (e = encodings; e; e = e->next) {
+ coding_type = (GType) GPOINTER_TO_SIZE (g_hash_table_lookup (decoder->priv->codings, e->data));
+ coding = g_object_new (coding_type,
+ SOUP_CODING_DIRECTION, SOUP_CODING_DECODE,
+ NULL);
+
+ /* Content-Encoding lists the codings in the order
+ * they were applied in, so we put decoders in reverse
+ * order so the last-applied will be the first
+ * decoded.
+ */
+ msgpriv->decoders = g_slist_prepend (msgpriv->decoders, coding);
+ }
+ soup_header_free_list (encodings);
+
+ soup_message_set_flags (msg, msgpriv->msg_flags | SOUP_MESSAGE_CONTENT_DECODED);
+}
+
+static void
+request_queued (SoupSessionFeature *feature, SoupSession *session,
+ SoupMessage *msg)
+{
+ SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (feature);
+
+ if (!soup_message_headers_get_one (msg->request_headers,
+ "Accept-Encoding")) {
+ soup_message_headers_append (msg->request_headers,
+ "Accept-Encoding",
+ ACCEPT_ENCODING_HEADER);
+ }
+
+ g_signal_connect (msg, "got-headers",
+ G_CALLBACK (soup_content_decoder_got_headers_cb),
+ decoder);
+}
+
+static void
+request_unqueued (SoupSessionFeature *feature, SoupSession *session,
+ SoupMessage *msg)
+{
+ g_signal_handlers_disconnect_by_func (msg, soup_content_decoder_got_headers_cb, feature);
+}
diff --git a/libsoup/soup-content-decoder.h b/libsoup/soup-content-decoder.h
new file mode 100644
index 00000000..e0b22383
--- /dev/null
+++ b/libsoup/soup-content-decoder.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef SOUP_CONTENT_DECODER_H
+#define SOUP_CONTENT_DECODER_H 1
+
+#include <libsoup/soup-types.h>
+#include <libsoup/soup-message-body.h>
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_CONTENT_DECODER (soup_content_decoder_get_type ())
+#define SOUP_CONTENT_DECODER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_CONTENT_DECODER, SoupContentDecoder))
+#define SOUP_CONTENT_DECODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CONTENT_DECODER, SoupContentDecoderClass))
+#define SOUP_IS_CONTENT_DECODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_CONTENT_DECODER))
+#define SOUP_IS_CONTENT_DECODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_CONTENT_DECODER))
+#define SOUP_CONTENT_DECODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONTENT_DECODER, SoupContentDecoderClass))
+
+typedef struct _SoupContentDecoderPrivate SoupContentDecoderPrivate;
+
+typedef struct {
+ GObject parent;
+
+ SoupContentDecoderPrivate *priv;
+} SoupContentDecoder;
+
+typedef struct {
+ GObjectClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_libsoup_reserved1) (void);
+ void (*_libsoup_reserved2) (void);
+ void (*_libsoup_reserved3) (void);
+ void (*_libsoup_reserved4) (void);
+ void (*_libsoup_reserved5) (void);
+} SoupContentDecoderClass;
+
+GType soup_content_decoder_get_type (void);
+
+G_END_DECLS
+
+#endif /* SOUP_CONTENT_DECODER_H */
diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c
index aae4e282..a804f439 100644
--- a/libsoup/soup-message-io.c
+++ b/libsoup/soup-message-io.c
@@ -12,6 +12,7 @@
#include <stdlib.h>
#include <string.h>
+#include "soup-coding.h"
#include "soup-connection.h"
#include "soup-message.h"
#include "soup-message-private.h"
@@ -345,6 +346,40 @@ read_metadata (SoupMessage *msg, gboolean to_blank)
return TRUE;
}
+static SoupBuffer *
+content_decode (SoupMessage *msg, SoupBuffer *buf)
+{
+ SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
+ SoupCoding *decoder;
+ SoupBuffer *decoded;
+ GError *error = NULL;
+ GSList *d;
+
+ for (d = priv->decoders; d; d = d->next) {
+ decoder = d->data;
+
+ decoded = soup_coding_apply (decoder, buf->data, buf->length,
+ FALSE, &error);
+ if (error) {
+ if (g_error_matches (error, SOUP_CODING_ERROR, SOUP_CODING_ERROR_INTERNAL_ERROR))
+ g_warning ("Content-Decoding error: %s\n", error->message);
+ g_error_free (error);
+
+ soup_message_set_flags (msg, priv->msg_flags & ~SOUP_MESSAGE_CONTENT_DECODED);
+ break;
+ }
+ if (buf)
+ soup_buffer_free (buf);
+
+ if (decoded)
+ buf = decoded;
+ else
+ return NULL;
+ }
+
+ return buf;
+}
+
/* Reads as much message body data as is available on io->sock (but no
* further than the end of the current message body or chunk). On a
* successful read, emits "got_chunk" (possibly multiple times), and
@@ -395,10 +430,14 @@ read_body_chunk (SoupMessage *msg)
if (status == SOUP_SOCKET_OK && nread) {
buffer->length = nread;
- soup_message_body_got_chunk (io->read_body, buffer);
-
io->read_length -= nread;
+ buffer = content_decode (msg, buffer);
+ if (!buffer)
+ continue;
+
+ soup_message_body_got_chunk (io->read_body, buffer);
+
if (io->need_content_sniffed) {
soup_message_body_append_buffer (io->sniff_data, buffer);
soup_buffer_free (buffer);
diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h
index 7375bd16..ee6221d5 100644
--- a/libsoup/soup-message-private.h
+++ b/libsoup/soup-message-private.h
@@ -40,6 +40,7 @@ typedef struct {
SoupAuth *auth, *proxy_auth;
GSList *disabled_features;
+ GSList *decoders;
} SoupMessagePrivate;
#define SOUP_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_MESSAGE, SoupMessagePrivate))
diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c
index 020577ab..93c25222 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -168,6 +168,11 @@ finalize (GObject *object)
g_slist_free (priv->disabled_features);
+ while (priv->decoders) {
+ g_object_unref (priv->decoders->data);
+ priv->decoders = g_slist_delete_link (priv->decoders, priv->decoders);
+ }
+
soup_message_body_free (msg->request_body);
soup_message_headers_free (msg->request_headers);
soup_message_body_free (msg->response_body);
@@ -1227,6 +1232,12 @@ soup_message_cleanup_response (SoupMessage *req)
SOUP_ENCODING_CONTENT_LENGTH);
}
+ while (priv->decoders) {
+ g_object_unref (priv->decoders->data);
+ priv->decoders = g_slist_delete_link (priv->decoders, priv->decoders);
+ }
+ priv->msg_flags &= ~SOUP_MESSAGE_CONTENT_DECODED;
+
req->status_code = SOUP_STATUS_NONE;
if (req->reason_phrase) {
g_free (req->reason_phrase);
@@ -1237,6 +1248,7 @@ soup_message_cleanup_response (SoupMessage *req)
g_object_notify (G_OBJECT (req), SOUP_MESSAGE_STATUS_CODE);
g_object_notify (G_OBJECT (req), SOUP_MESSAGE_REASON_PHRASE);
g_object_notify (G_OBJECT (req), SOUP_MESSAGE_HTTP_VERSION);
+ g_object_notify (G_OBJECT (req), SOUP_MESSAGE_FLAGS);
}
/**
diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h
index 4bbcb1be..4fc91227 100644
--- a/libsoup/soup-message.h
+++ b/libsoup/soup-message.h
@@ -101,10 +101,11 @@ void soup_message_set_uri (SoupMessage *msg,
SoupAddress *soup_message_get_address (SoupMessage *msg);
typedef enum {
+ SOUP_MESSAGE_NO_REDIRECT = (1 << 1),
#ifndef LIBSOUP_DISABLE_DEPRECATED
SOUP_MESSAGE_OVERWRITE_CHUNKS = (1 << 3),
#endif
- SOUP_MESSAGE_NO_REDIRECT = (1 << 1)
+ SOUP_MESSAGE_CONTENT_DECODED = (1 << 4)
} SoupMessageFlags;
void soup_message_set_flags (SoupMessage *msg,
diff --git a/libsoup/soup.h b/libsoup/soup.h
index f3b0b3d5..e7273614 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -15,6 +15,7 @@ extern "C" {
#include <libsoup/soup-auth-domain.h>
#include <libsoup/soup-auth-domain-basic.h>
#include <libsoup/soup-auth-domain-digest.h>
+#include <libsoup/soup-content-decoder.h>
#include <libsoup/soup-content-sniffer.h>
#include <libsoup/soup-cookie.h>
#include <libsoup/soup-cookie-jar.h>
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 20716c24..7014bdc8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -13,6 +13,7 @@ LIBS = \
noinst_PROGRAMS = \
chunk-test \
+ coding-test \
context-test \
continue-test \
date \
@@ -39,6 +40,7 @@ TEST_SRCS = test-utils.c test-utils.h
auth_test_SOURCES = auth-test.c $(TEST_SRCS)
chunk_test_SOURCES = chunk-test.c $(TEST_SRCS)
+coding_test_SOURCES = coding-test.c $(TEST_SRCS)
context_test_SOURCES = context-test.c $(TEST_SRCS)
continue_test_SOURCES = continue-test.c $(TEST_SRCS)
date_SOURCES = date.c $(TEST_SRCS)
@@ -82,6 +84,7 @@ endif
TESTS = \
chunk-test \
+ coding-test \
context-test \
continue-test \
date \
diff --git a/tests/coding-test.c b/tests/coding-test.c
new file mode 100644
index 00000000..47efb4a2
--- /dev/null
+++ b/tests/coding-test.c
@@ -0,0 +1,154 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Red Hat, Inc.
+ */
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <libsoup/soup.h>
+
+#include "test-utils.h"
+
+SoupServer *server;
+SoupURI *base_uri;
+
+static void
+server_callback (SoupServer *server, SoupMessage *msg,
+ const char *path, GHashTable *query,
+ SoupClientContext *context, gpointer data)
+{
+ const char *accept_encoding;
+ GSList *codings;
+ char *file = NULL, *contents;
+ gsize length;
+
+ accept_encoding = soup_message_headers_get_list (msg->request_headers,
+ "Accept-Encoding");
+ if (accept_encoding)
+ codings = soup_header_parse_quality_list (accept_encoding, NULL);
+ else
+ codings = NULL;
+
+ if (codings && g_slist_find_custom (codings, "gzip", (GCompareFunc)g_ascii_strcasecmp)) {
+ file = g_strdup_printf (SRCDIR "/resources%s.gz", path);
+ if (g_file_test (file, G_FILE_TEST_EXISTS)) {
+ soup_message_headers_append (msg->response_headers,
+ "Content-Encoding",
+ "gzip");
+ } else {
+ g_free (file);
+ file = NULL;
+ }
+ }
+
+ if (!file)
+ file = g_strdup_printf (SRCDIR "/resources%s", path);
+ if (!g_file_get_contents (file, &contents, &length, NULL)) {
+ /* If path.gz exists but can't be read, we'll send back
+ * the error with "Content-Encoding: gzip" but there's
+ * no body, so, eh.
+ */
+ soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+ return;
+ }
+
+ soup_message_set_status (msg, SOUP_STATUS_OK);
+ soup_message_body_append (msg->response_body,
+ SOUP_MEMORY_TAKE, contents, length);
+}
+
+static void
+do_coding_test (void)
+{
+ SoupSession *session;
+ SoupMessage *msg, *msgz;
+ SoupURI *uri;
+ const char *coding;
+
+ session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL);
+ uri = soup_uri_new_with_base (base_uri, "/mbox");
+
+ debug_printf (1, "GET /mbox, plain\n");
+ msg = soup_message_new_from_uri ("GET", uri);
+ soup_session_send_message (session, msg);
+ if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
+ debug_printf (1, " Unexpected status %d %s\n",
+ msg->status_code, msg->reason_phrase);
+ errors++;
+ }
+ coding = soup_message_headers_get_one (msg->response_headers, "Content-Encoding");
+ if (coding) {
+ debug_printf (1, " Unexpected Content-Encoding: %s\n",
+ coding);
+ errors++;
+ }
+ if (soup_message_get_flags (msg) & SOUP_MESSAGE_CONTENT_DECODED) {
+ debug_printf (1, " SOUP_MESSAGE_CONTENT_DECODED set!\n");
+ errors++;
+ }
+
+ debug_printf (1, "GET /mbox, Accept-Encoding: gzip\n");
+ soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER);
+ msgz = soup_message_new_from_uri ("GET", uri);
+ soup_session_send_message (session, msgz);
+ if (!SOUP_STATUS_IS_SUCCESSFUL (msgz->status_code)) {
+ debug_printf (1, " Unexpected status %d %s\n",
+ msgz->status_code, msgz->reason_phrase);
+ errors++;
+ }
+ coding = soup_message_headers_get_one (msgz->response_headers, "Content-Encoding");
+ if (!coding || g_ascii_strcasecmp (coding, "gzip") != 0) {
+ debug_printf (1, " Unexpected Content-Encoding: %s\n",
+ coding ? coding : "(none)");
+ errors++;
+ }
+ if (!(soup_message_get_flags (msgz) & SOUP_MESSAGE_CONTENT_DECODED)) {
+ debug_printf (1, " SOUP_MESSAGE_CONTENT_DECODED not set!\n");
+ errors++;
+ }
+
+ if (msg->response_body->length != msgz->response_body->length) {
+ debug_printf (1, " Message length mismatch: %lu (plain) vs %lu (compressed)\n",
+ (gulong)msg->response_body->length,
+ (gulong)msgz->response_body->length);
+ errors++;
+ } else if (memcmp (msg->response_body->data,
+ msgz->response_body->data,
+ msg->response_body->length) != 0) {
+ debug_printf (1, " Message data mismatch\n");
+ errors++;
+ }
+
+ g_object_unref (msg);
+ g_object_unref (msgz);
+ soup_uri_free (uri);
+
+ soup_test_session_abort_unref (session);
+}
+
+int
+main (int argc, char **argv)
+{
+ test_init (argc, argv, NULL);
+
+ server = soup_test_server_new (TRUE);
+ soup_server_add_handler (server, NULL, server_callback, NULL, NULL);
+ base_uri = soup_uri_new ("http://127.0.0.1/");
+ soup_uri_set_port (base_uri, soup_server_get_port (server));
+
+ do_coding_test ();
+
+ soup_uri_free (base_uri);
+
+ test_cleanup ();
+ return errors != 0;
+}
diff --git a/tests/get.c b/tests/get.c
index b0e5c576..98f66f6e 100644
--- a/tests/get.c
+++ b/tests/get.c
@@ -48,6 +48,11 @@ get_url (const char *url)
printf ("%s %s HTTP/1.%d\n\n", method, path,
soup_message_get_http_version (msg));
+ soup_message_headers_iter_init (&iter, msg->request_headers);
+ while (soup_message_headers_iter_next (&iter, &hname, &value))
+ printf ("%s: %s\r\n", hname, value);
+ printf ("\n");
+
printf ("HTTP/1.%d %d %s\n",
soup_message_get_http_version (msg),
msg->status_code, msg->reason_phrase);
@@ -145,6 +150,7 @@ main (int argc, char **argv)
#ifdef HAVE_GNOME
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26,
#endif
+ SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
SOUP_SESSION_USER_AGENT, "get ",
NULL);
} else {
@@ -153,6 +159,7 @@ main (int argc, char **argv)
#ifdef HAVE_GNOME
SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_GNOME_FEATURES_2_26,
#endif
+ SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER,
SOUP_SESSION_USER_AGENT, "get ",
NULL);
}
diff --git a/tests/resources/mbox.gz b/tests/resources/mbox.gz
new file mode 100644
index 00000000..1a70d069
--- /dev/null
+++ b/tests/resources/mbox.gz
Binary files differ