diff options
| author | Dan Winship <danw@gnome.org> | 2008-03-15 09:52:35 -0400 |
|---|---|---|
| committer | Dan Winship <danw@gnome.org> | 2009-10-27 13:36:31 -0400 |
| commit | bcda64751ce13b8c5513b6f8ec3f2a8814d0d23c (patch) | |
| tree | b3e3f458e829ab39049de59aa3d99a74e974595c | |
| parent | 1866a401a37270a441e5661c87e32323fa2d7aa2 (diff) | |
| download | libsoup-bcda64751ce13b8c5513b6f8ec3f2a8814d0d23c.tar.gz | |
Content-Encoding support
| -rw-r--r-- | libsoup/Makefile.am | 10 | ||||
| -rw-r--r-- | libsoup/soup-coding-deflate.c | 139 | ||||
| -rw-r--r-- | libsoup/soup-coding-deflate.h | 33 | ||||
| -rw-r--r-- | libsoup/soup-coding-gzip.c | 49 | ||||
| -rw-r--r-- | libsoup/soup-coding-gzip.h | 33 | ||||
| -rw-r--r-- | libsoup/soup-coding-zlib.c | 147 | ||||
| -rw-r--r-- | libsoup/soup-coding-zlib.h | 34 | ||||
| -rw-r--r-- | libsoup/soup-coding.c | 258 | ||||
| -rw-r--r-- | libsoup/soup-coding.h | 92 | ||||
| -rw-r--r-- | libsoup/soup-content-decoder.c | 168 | ||||
| -rw-r--r-- | libsoup/soup-content-decoder.h | 44 | ||||
| -rw-r--r-- | libsoup/soup-message-io.c | 41 | ||||
| -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/get.c | 31 | ||||
| -rw-r--r-- | tests/httpd.conf.in | 8 |
18 files changed, 1099 insertions, 5 deletions
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index c25b534c..47864989 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,17 @@ 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-deflate.h \ + soup-coding-deflate.c \ + soup-coding-gzip.h \ + soup-coding-gzip.c \ + soup-coding-zlib.h \ + soup-coding-zlib.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-deflate.c b/libsoup/soup-coding-deflate.c new file mode 100644 index 00000000..68e8955d --- /dev/null +++ b/libsoup/soup-coding-deflate.c @@ -0,0 +1,139 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-coding-deflate.c: "deflate" coding + * + * Copyright (C) 2005 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "soup-coding-deflate.h" + +typedef struct { + gboolean decode_inited, havebyte; + guchar byte[2]; + +} SoupCodingDeflatePrivate; +#define SOUP_CODING_DEFLATE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CODING_DEFLATE, SoupCodingDeflatePrivate)) + +G_DEFINE_TYPE (SoupCodingDeflate, soup_coding_deflate, SOUP_TYPE_CODING_ZLIB) + +static SoupCodingClass *zlib_class; + +static void constructed (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_deflate_init (SoupCodingDeflate *deflate) +{ +} + +static void +soup_coding_deflate_class_init (SoupCodingDeflateClass *deflate_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (deflate_class); + SoupCodingClass *coding_class = SOUP_CODING_CLASS (deflate_class); + + g_type_class_add_private (deflate_class, sizeof (SoupCodingDeflatePrivate)); + + coding_class->name = "deflate"; + + zlib_class = SOUP_CODING_CLASS (soup_coding_deflate_parent_class); + + object_class->constructed = constructed; + + coding_class->apply_into = apply_into; +} + +static void +constructed (GObject *object) +{ + if (SOUP_CODING (object)->direction == SOUP_CODING_ENCODE) + soup_coding_zlib_init_encode (SOUP_CODING_ZLIB (object), 15); +} + +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) +{ + SoupCodingDeflatePrivate *priv = + SOUP_CODING_DEFLATE_GET_PRIVATE (coding); + + if (coding->direction == SOUP_CODING_DECODE && !priv->decode_inited) { + /* The "deflate" content/transfer coding is supposed + * to be the RFC1950 "zlib" format wrapping the + * RFC1951 "deflate" method. However, apparently many + * implementations have gotten this wrong and used raw + * "deflate" instead. So we check the first two bytes + * to see if they look like a zlib header, and fall + * back to raw deflate if they don't. This is slightly + * tricky because in the pathological case, we might + * be called with only a single byte on the first + * call. + */ + + if (input_length == 0) { + *input_used = *output_used = 0; + return SOUP_CODING_STATUS_OK; + } else if (input_length == 1 && !priv->havebyte) { + priv->byte[0] = ((guchar *)input)[0]; + priv->havebyte = TRUE; + *input_used = 1; + *output_used = 0; + return SOUP_CODING_STATUS_OK; + } + + /* OK, one way or another we have two bytes now. */ + if (priv->havebyte) + priv->byte[1] = ((guchar *)input)[0]; + else { + priv->byte[0] = ((guchar *)input)[0]; + priv->byte[1] = ((guchar *)input)[1]; + } + + /* A "zlib" stream starts with 0x8 in the lower three + * bits of the first byte, and the first two bytes + * together taken as a big-endian integer are + * divisible by 31. + */ + if (((priv->byte[0] & 0x8) == 0x8) && + ((priv->byte[0] << 8 & priv->byte[1]) % 31 == 0)) { + /* "zlib" */ + soup_coding_zlib_init_decode (SOUP_CODING_ZLIB (coding), 15); + } else { + /* "deflate" */ + soup_coding_zlib_init_decode (SOUP_CODING_ZLIB (coding), -15); + } + + priv->decode_inited = TRUE; + + if (priv->havebyte) { + gsize dummy_input_used; + + /* Push the previously-read byte into the + * coder and then force the caller to call us + * again with the same input data. + */ + *input_used = 0; + return zlib_class->apply_into ( + coding, priv->byte, 1, &dummy_input_used, + output, output_length, output_used, + done, error); + } + } + + return zlib_class->apply_into ( + coding, input, input_length, input_used, + output, output_length, output_used, + done, error); +} diff --git a/libsoup/soup-coding-deflate.h b/libsoup/soup-coding-deflate.h new file mode 100644 index 00000000..748c44b9 --- /dev/null +++ b/libsoup/soup-coding-deflate.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_DEFLATE_H +#define SOUP_CODING_DEFLATE_H 1 + +#include "soup-coding-zlib.h" + +#define SOUP_TYPE_CODING_DEFLATE (soup_coding_deflate_get_type ()) +#define SOUP_CODING_DEFLATE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_CODING_DEFLATE, SoupCodingDeflate)) +#define SOUP_CODING_DEFLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CODING_DEFLATE, SoupCodingDeflateClass)) +#define SOUP_IS_CODING_DEFLATE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_CODING_DEFLATE)) +#define SOUP_IS_CODING_DEFLATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CODING_DEFLATE)) +#define SOUP_CODING_DEFLATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CODING_DEFLATE, SoupCodingDeflateClass)) + +typedef struct { + SoupCodingZLib parent; + +} SoupCodingDeflate; + +typedef struct { + SoupCodingZLibClass parent_class; + +} SoupCodingDeflateClass; + +GType soup_coding_deflate_get_type (void); + +SoupCoding *soup_coding_deflate_new (SoupCodingDirection direction); + +#endif /* SOUP_CODING_DEFLATE_H */ diff --git a/libsoup/soup-coding-gzip.c b/libsoup/soup-coding-gzip.c new file mode 100644 index 00000000..193a66f5 --- /dev/null +++ b/libsoup/soup-coding-gzip.c @@ -0,0 +1,49 @@ +/* -*- 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" + +G_DEFINE_TYPE (SoupCodingGzip, soup_coding_gzip, SOUP_TYPE_CODING_ZLIB) + +static void constructed (GObject *object); + +static void +soup_coding_gzip_init (SoupCodingGzip *gzip) +{ +} + +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); + + coding_class->name = "gzip"; + + object_class->constructed = constructed; +} + +static void +constructed (GObject *object) +{ + guint flags; + + /* More zlib magic numbers: 15 just because, and 16 to indicate + * gzip format. + */ + flags = 15 | 16; + + if (SOUP_CODING (object)->direction == SOUP_CODING_ENCODE) + soup_coding_zlib_init_encode (SOUP_CODING_ZLIB (object), flags); + else + soup_coding_zlib_init_decode (SOUP_CODING_ZLIB (object), flags); +} diff --git a/libsoup/soup-coding-gzip.h b/libsoup/soup-coding-gzip.h new file mode 100644 index 00000000..51d3467c --- /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-zlib.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 { + SoupCodingZLib parent; + +} SoupCodingGzip; + +typedef struct { + SoupCodingZLibClass 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-zlib.c b/libsoup/soup-coding-zlib.c new file mode 100644 index 00000000..a1db0abf --- /dev/null +++ b/libsoup/soup-coding-zlib.c @@ -0,0 +1,147 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-coding-zlib.c: Base class for gzip and deflate codings + * + * Copyright (C) 2005 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "soup-coding-zlib.h" + +#include <zlib.h> + +typedef struct { + z_stream stream; + +} SoupCodingZLibPrivate; +#define SOUP_CODING_ZLIB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CODING_ZLIB, SoupCodingZLibPrivate)) + +G_DEFINE_TYPE (SoupCodingZLib, soup_coding_zlib, SOUP_TYPE_CODING) + +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 finalize (GObject *object); + +static void +soup_coding_zlib_init (SoupCodingZLib *coding) +{ + SoupCodingZLibPrivate *priv = SOUP_CODING_ZLIB_GET_PRIVATE (coding); + + priv->stream.zalloc = Z_NULL; + priv->stream.zfree = Z_NULL; + priv->stream.opaque = Z_NULL; +} + +static void +soup_coding_zlib_class_init (SoupCodingZLibClass *zlib_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (zlib_class); + SoupCodingClass *coding_class = SOUP_CODING_CLASS (zlib_class); + + g_type_class_add_private (zlib_class, sizeof (SoupCodingZLibPrivate)); + + object_class->finalize = finalize; + + coding_class->apply_into = apply_into; +} + +static void +finalize (GObject *object) +{ + SoupCodingZLibPrivate *priv = SOUP_CODING_ZLIB_GET_PRIVATE (object); + + if (SOUP_CODING (object)->direction == SOUP_CODING_ENCODE) + deflateEnd (&priv->stream); + else + inflateEnd (&priv->stream); + + G_OBJECT_CLASS (soup_coding_zlib_parent_class)->finalize (object); +} + +void +soup_coding_zlib_init_encode (SoupCodingZLib *coding, int flags) +{ + SoupCodingZLibPrivate *priv = SOUP_CODING_ZLIB_GET_PRIVATE (coding); + + priv->stream.next_in = Z_NULL; + priv->stream.avail_in = 0; + /* All of these values are the defaults according to the zlib + * documentation. "flags" distinguishes between "zlib" and + * "gzip" formats. + */ + deflateInit2 (&priv->stream, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, flags, 8, Z_DEFAULT_STRATEGY); +} + +void +soup_coding_zlib_init_decode (SoupCodingZLib *coding, int flags) +{ + SoupCodingZLibPrivate *priv = SOUP_CODING_ZLIB_GET_PRIVATE (coding); + + priv->stream.next_in = Z_NULL; + priv->stream.avail_in = 0; + inflateInit2 (&priv->stream, flags); +} + +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) +{ + SoupCodingZLibPrivate *priv = SOUP_CODING_ZLIB_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: + 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_STREAM_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-zlib.h b/libsoup/soup-coding-zlib.h new file mode 100644 index 00000000..0c9c2261 --- /dev/null +++ b/libsoup/soup-coding-zlib.h @@ -0,0 +1,34 @@ +/* -*- 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_ZLIB_H +#define SOUP_CODING_ZLIB_H 1 + +#include "soup-coding.h" + +#define SOUP_TYPE_CODING_ZLIB (soup_coding_zlib_get_type ()) +#define SOUP_CODING_ZLIB(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_CODING_ZLIB, SoupCodingZLib)) +#define SOUP_CODING_ZLIB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CODING_ZLIB, SoupCodingZLibClass)) +#define SOUP_IS_CODING_ZLIB(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_CODING_ZLIB)) +#define SOUP_IS_CODING_ZLIB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CODING_ZLIB)) +#define SOUP_CODING_ZLIB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CODING_ZLIB, SoupCodingZLibClass)) + +typedef struct { + SoupCoding parent; + +} SoupCodingZLib; + +typedef struct { + SoupCodingClass parent_class; + +} SoupCodingZLibClass; + +GType soup_coding_zlib_get_type (void); + +void soup_coding_zlib_init_encode (SoupCodingZLib *coding, int flags); +void soup_coding_zlib_init_decode (SoupCodingZLib *coding, int flags); + +#endif /* SOUP_CODING_ZLIB_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..43eb0d81 --- /dev/null +++ b/libsoup/soup-content-decoder.c @@ -0,0 +1,168 @@ +/* -*- 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-deflate.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,deflate" + +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, "deflate", + GSIZE_TO_POINTER (SOUP_TYPE_CODING_DEFLATE)); + 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..5e4ae6c4 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,38 @@ 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) { + // FIXME! + g_warning ("Content-Decoding error: %s\n", error->message); + g_error_free (error); + 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 +428,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/get.c b/tests/get.c index b0e5c576..ca072e33 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); @@ -83,7 +88,7 @@ usage (void) int main (int argc, char **argv) { - const char *cafile = NULL, *url; + const char *cafile = NULL, *cookiefile = NULL, *url; SoupURI *proxy = NULL, *parsed; gboolean synchronous = FALSE; int opt; @@ -93,12 +98,16 @@ main (int argc, char **argv) method = SOUP_METHOD_GET; - while ((opt = getopt (argc, argv, "c:dhp:s")) != -1) { + while ((opt = getopt (argc, argv, "c:C:dhp:s")) != -1) { switch (opt) { case 'c': cafile = optarg; break; + case 'C': + cookiefile = optarg; + break; + case 'd': debug = TRUE; break; @@ -145,6 +154,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 +163,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); } @@ -167,6 +178,22 @@ main (int argc, char **argv) NULL); } + if (cookiefile) { + SoupCookieJar *jar; + +#ifdef HAVE_GNOME + if (g_str_has_suffix (cookiefile, ".sqlite")) + jar = soup_cookie_jar_sqlite_new (cookiefile, TRUE); + else +#endif + jar = soup_cookie_jar_text_new (cookiefile, TRUE); + + if (jar) { + soup_session_add_feature (session, SOUP_SESSION_FEATURE (jar)); + g_object_unref (jar); + } + } + if (!synchronous) loop = g_main_loop_new (NULL, TRUE); diff --git a/tests/httpd.conf.in b/tests/httpd.conf.in index b893fdc0..7cba8c83 100644 --- a/tests/httpd.conf.in +++ b/tests/httpd.conf.in @@ -20,6 +20,7 @@ LoadModule auth_digest_module @APACHE_MODULE_DIR@/mod_auth_digest.so LoadModule authn_file_module @APACHE_MODULE_DIR@/mod_authn_file.so LoadModule authz_host_module @APACHE_MODULE_DIR@/mod_authz_host.so LoadModule authz_user_module @APACHE_MODULE_DIR@/mod_authz_user.so +LoadModule deflate_module @APACHE_MODULE_DIR@/mod_deflate.so LoadModule dir_module @APACHE_MODULE_DIR@/mod_dir.so LoadModule mime_module @APACHE_MODULE_DIR@/mod_mime.so @IF_HAVE_PHP@LoadModule php5_module @APACHE_PHP_MODULE_DIR@/@APACHE_PHP_MODULE@ @@ -282,3 +283,10 @@ Alias /Digest @srcdir@ # test RFC2069-style Digest AuthDigestQop none </Location> + +# Compression +Alias /compressed @srcdir@ + +<Location /compressed> + SetOutputFilter DEFLATE +</Location> |
