[libsoup] Content-Encoding support



commit 04e927a174b7f40c97a8106ad3f64a729abe952b
Author: Dan Winship <danw gnome org>
Date:   Sat Mar 15 09:52:35 2008 -0400

    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

 .gitignore                     |   18 +---
 libsoup/Makefile.am            |    6 +
 libsoup/soup-coding-gzip.c     |  142 ++++++++++++++++++++++
 libsoup/soup-coding-gzip.h     |   33 +++++
 libsoup/soup-coding.c          |  258 ++++++++++++++++++++++++++++++++++++++++
 libsoup/soup-coding.h          |   92 ++++++++++++++
 libsoup/soup-content-decoder.c |  165 +++++++++++++++++++++++++
 libsoup/soup-content-decoder.h |   44 +++++++
 libsoup/soup-message-io.c      |   43 +++++++-
 libsoup/soup-message-private.h |    1 +
 libsoup/soup-message.c         |   12 ++
 libsoup/soup-message.h         |    3 +-
 libsoup/soup.h                 |    1 +
 tests/Makefile.am              |    3 +
 tests/coding-test.c            |  154 ++++++++++++++++++++++++
 tests/get.c                    |    7 +
 tests/resources/mbox.gz        |  Bin 0 -> 310 bytes
 17 files changed, 962 insertions(+), 20 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 9377d34..f597375 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 c25b534..53fae83 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 0000000..b1731d5
--- /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 0000000..abdca37
--- /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 0000000..74c4826
--- /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 0000000..a00d014
--- /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 0000000..5ebcff6
--- /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 0000000..e0b2238
--- /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 aae4e28..a804f43 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 7375bd1..ee6221d 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 020577a..93c2522 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 4bbcb1b..4fc9122 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 f3b0b3d..e727361 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 20716c2..7014bdc 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 0000000..47efb4a
--- /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 b0e5c57..98f66f6 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 0000000..1a70d06
Binary files /dev/null and b/tests/resources/mbox.gz differ



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]