diff options
-rw-r--r-- | .gitignore | 18 | ||||
-rw-r--r-- | libsoup/Makefile.am | 6 | ||||
-rw-r--r-- | libsoup/soup-coding-gzip.c | 142 | ||||
-rw-r--r-- | libsoup/soup-coding-gzip.h | 33 | ||||
-rw-r--r-- | libsoup/soup-coding.c | 258 | ||||
-rw-r--r-- | libsoup/soup-coding.h | 92 | ||||
-rw-r--r-- | libsoup/soup-content-decoder.c | 165 | ||||
-rw-r--r-- | libsoup/soup-content-decoder.h | 44 | ||||
-rw-r--r-- | libsoup/soup-message-io.c | 43 | ||||
-rw-r--r-- | libsoup/soup-message-private.h | 1 | ||||
-rw-r--r-- | libsoup/soup-message.c | 12 | ||||
-rw-r--r-- | libsoup/soup-message.h | 3 | ||||
-rw-r--r-- | libsoup/soup.h | 1 | ||||
-rw-r--r-- | tests/Makefile.am | 3 | ||||
-rw-r--r-- | tests/coding-test.c | 154 | ||||
-rw-r--r-- | tests/get.c | 7 | ||||
-rw-r--r-- | tests/resources/mbox.gz | bin | 0 -> 310 bytes |
17 files changed, 962 insertions, 20 deletions
@@ -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 Binary files differnew file mode 100644 index 00000000..1a70d069 --- /dev/null +++ b/tests/resources/mbox.gz |