summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Kolesa <dkolesa@igalia.com>2021-03-04 13:31:17 +0100
committerDaniel Kolesa <dkolesa@igalia.com>2021-03-09 14:29:49 +0100
commitc61f62f9bae4a78f38266b41fc3af1a4acb2c929 (patch)
tree4fa9d985cb51c8c3f36e171f4d626fd19f731f7b
parent12cfc749653ab2b869df2ec20c9b10e1264894e7 (diff)
downloadlibsoup-c61f62f9bae4a78f38266b41fc3af1a4acb2c929.tar.gz
logger: re-add support for logging request/response bodies
-rw-r--r--docs/reference/libsoup-3.0-sections.txt2
-rw-r--r--docs/reference/meson.build2
-rw-r--r--libsoup/meson.build1
-rw-r--r--libsoup/soup-body-output-stream.c12
-rw-r--r--libsoup/soup-logger-input-stream.c229
-rw-r--r--libsoup/soup-logger-input-stream.h21
-rw-r--r--libsoup/soup-logger-private.h14
-rw-r--r--libsoup/soup-logger.c279
-rw-r--r--libsoup/soup-logger.h10
-rw-r--r--libsoup/soup-message-io.c11
-rw-r--r--tests/logger-test.c201
-rw-r--r--tests/test-utils.c2
12 files changed, 751 insertions, 33 deletions
diff --git a/docs/reference/libsoup-3.0-sections.txt b/docs/reference/libsoup-3.0-sections.txt
index 0d996bae..0e963653 100644
--- a/docs/reference/libsoup-3.0-sections.txt
+++ b/docs/reference/libsoup-3.0-sections.txt
@@ -553,6 +553,8 @@ soup_form_encode_valist
SoupLogger
SoupLoggerLogLevel
soup_logger_new
+soup_logger_set_max_body_size
+soup_logger_get_max_body_size
<SUBSECTION>
SoupLoggerFilter
soup_logger_set_request_filter
diff --git a/docs/reference/meson.build b/docs/reference/meson.build
index 812042fa..40a98944 100644
--- a/docs/reference/meson.build
+++ b/docs/reference/meson.build
@@ -23,6 +23,8 @@ ignore_headers = [
'soup-cookie-jar-sqlite.h',
'soup-cache-private.h',
'soup-cache-client-input-stream.h',
+ 'soup-logger-input-stream.h',
+ 'soup-logger-private.h',
'soup-socket.h',
'soup-socket-properties.h',
'soup-websocket-extension-manager-private.h',
diff --git a/libsoup/meson.build b/libsoup/meson.build
index 48b8e596..827c4a3b 100644
--- a/libsoup/meson.build
+++ b/libsoup/meson.build
@@ -59,6 +59,7 @@ soup_sources = [
'soup-init.c',
'soup-io-stream.c',
'soup-logger.c',
+ 'soup-logger-input-stream.c',
'soup-message.c',
'soup-message-headers.c',
'soup-message-io.c',
diff --git a/libsoup/soup-body-output-stream.c b/libsoup/soup-body-output-stream.c
index a390ed83..c1c0e4a0 100644
--- a/libsoup/soup-body-output-stream.c
+++ b/libsoup/soup-body-output-stream.c
@@ -115,9 +115,10 @@ soup_body_output_stream_get_property (GObject *object, guint prop_id,
static void
soup_body_output_stream_wrote_data (SoupBodyOutputStream *bostream,
- gsize count)
+ const void *buffer,
+ gsize count)
{
- g_signal_emit (bostream, signals[WROTE_DATA], 0, count);
+ g_signal_emit (bostream, signals[WROTE_DATA], 0, buffer, count);
}
static gssize
@@ -150,7 +151,7 @@ soup_body_output_stream_write_raw (SoupBodyOutputStream *bostream,
if (nwrote > 0 && priv->write_length) {
priv->written += nwrote;
- soup_body_output_stream_wrote_data (bostream, nwrote);
+ soup_body_output_stream_wrote_data (bostream, buffer, nwrote);
}
if (nwrote == my_count && my_count != count)
@@ -199,7 +200,7 @@ again:
buffer, count, blocking,
cancellable, error);
if (nwrote > 0)
- soup_body_output_stream_wrote_data (bostream, nwrote);
+ soup_body_output_stream_wrote_data (bostream, buffer, nwrote);
if (nwrote < (gssize)count)
return nwrote;
@@ -340,7 +341,8 @@ soup_body_output_stream_class_init (SoupBodyOutputStreamClass *stream_class)
0,
NULL, NULL,
NULL,
- G_TYPE_NONE, 1,
+ G_TYPE_NONE, 2,
+ G_TYPE_POINTER,
G_TYPE_UINT);
g_object_class_install_property (
diff --git a/libsoup/soup-logger-input-stream.c b/libsoup/soup-logger-input-stream.c
new file mode 100644
index 00000000..6b7c6100
--- /dev/null
+++ b/libsoup/soup-logger-input-stream.c
@@ -0,0 +1,229 @@
+/* soup-logger-stream.c
+ *
+ * Copyright (C) 2021 Igalia S.L.
+ */
+
+#include <string.h>
+
+#include "soup-logger-input-stream.h"
+#include "soup.h"
+
+enum {
+ PROP_LOGGER = 1
+};
+
+enum {
+ READ_DATA,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _SoupLoggerInputStream {
+ GFilterInputStream parent;
+};
+
+typedef struct {
+ SoupLogger *logger;
+ GByteArray *buffer; /* for skip; we still need to log it */
+} SoupLoggerInputStreamPrivate;
+
+static void soup_logger_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SoupLoggerInputStream, soup_logger_input_stream, G_TYPE_FILTER_INPUT_STREAM,
+ G_ADD_PRIVATE (SoupLoggerInputStream)
+ G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM,
+ soup_logger_input_stream_pollable_init))
+
+static void
+soup_logger_input_stream_init (SoupLoggerInputStream *logger)
+{
+}
+
+static void
+soup_logger_input_stream_finalize (GObject *object)
+{
+ SoupLoggerInputStream *stream = SOUP_LOGGER_INPUT_STREAM (object);
+ SoupLoggerInputStreamPrivate *priv = soup_logger_input_stream_get_instance_private (stream);
+
+ g_clear_object (&priv->logger);
+
+ g_clear_pointer (&priv->buffer, g_byte_array_unref);
+
+ G_OBJECT_CLASS (soup_logger_input_stream_parent_class)->finalize (object);
+}
+
+static void
+soup_logger_input_stream_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupLoggerInputStream *stream = SOUP_LOGGER_INPUT_STREAM (object);
+ SoupLoggerInputStreamPrivate *priv = soup_logger_input_stream_get_instance_private (stream);
+
+ switch (prop_id) {
+ case PROP_LOGGER:
+ priv->logger = g_value_dup_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_logger_input_stream_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupLoggerInputStream *stream = SOUP_LOGGER_INPUT_STREAM (object);
+ SoupLoggerInputStreamPrivate *priv = soup_logger_input_stream_get_instance_private (stream);
+
+ switch (prop_id) {
+ case PROP_LOGGER:
+ g_value_set_object (value, priv->logger);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gssize
+read_internal (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ gboolean blocking,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupLoggerInputStream *lstream = SOUP_LOGGER_INPUT_STREAM (stream);
+ gssize nread;
+
+ nread = g_pollable_stream_read (G_FILTER_INPUT_STREAM (stream)->base_stream,
+ buffer, count, blocking, cancellable, error);
+
+ if (nread > 0)
+ g_signal_emit (lstream, signals[READ_DATA], 0, buffer, nread);
+
+ return nread;
+}
+
+static gssize
+soup_logger_input_stream_read (GInputStream *stream,
+ void *buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return read_internal (stream, buffer, count, TRUE, cancellable, error);
+}
+
+static gssize
+soup_logger_input_stream_skip (GInputStream *stream,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ SoupLoggerInputStream *lstream = SOUP_LOGGER_INPUT_STREAM (stream);
+ SoupLoggerInputStreamPrivate *priv = soup_logger_input_stream_get_instance_private (lstream);
+
+ if (!priv->buffer)
+ priv->buffer = g_byte_array_sized_new (count);
+ else
+ g_byte_array_set_size (priv->buffer, count);
+
+ return read_internal (stream, priv->buffer->data, count, TRUE, cancellable, error);
+}
+
+static gboolean
+soup_logger_input_stream_can_poll (GPollableInputStream *stream)
+{
+ GInputStream *base_stream = G_FILTER_INPUT_STREAM (stream)->base_stream;
+
+ return G_IS_POLLABLE_INPUT_STREAM (base_stream) &&
+ g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (base_stream));
+}
+
+static gboolean
+soup_logger_input_stream_is_readable (GPollableInputStream *stream)
+{
+ return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream));
+}
+
+static gssize
+soup_logger_input_stream_read_nonblocking (GPollableInputStream *stream,
+ void *buffer,
+ gsize count,
+ GError **error)
+{
+ return read_internal (G_INPUT_STREAM (stream),
+ buffer, count, FALSE, NULL, error);
+}
+
+static GSource *
+soup_logger_input_stream_create_source (GPollableInputStream *stream,
+ GCancellable *cancellable)
+{
+ GSource *base_source, *pollable_source;
+
+ base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (stream)->base_stream),
+ cancellable);
+
+ g_source_set_dummy_callback (base_source);
+ pollable_source = g_pollable_source_new (G_OBJECT (stream));
+ g_source_add_child_source (pollable_source, base_source);
+ g_source_unref (base_source);
+
+ return pollable_source;
+}
+
+static void
+soup_logger_input_stream_class_init (SoupLoggerInputStreamClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GInputStreamClass *input_stream_class = G_INPUT_STREAM_CLASS (klass);
+
+ object_class->finalize = soup_logger_input_stream_finalize;
+ object_class->set_property = soup_logger_input_stream_set_property;
+ object_class->get_property = soup_logger_input_stream_get_property;
+
+ input_stream_class->read_fn = soup_logger_input_stream_read;
+ input_stream_class->skip = soup_logger_input_stream_skip;
+
+ signals[READ_DATA] =
+ g_signal_new ("read-data",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE,
+ 2,
+ G_TYPE_POINTER, G_TYPE_INT);
+
+ g_object_class_install_property (
+ object_class, PROP_LOGGER,
+ g_param_spec_object ("logger",
+ "Logger",
+ "The stream's SoupLogger",
+ SOUP_TYPE_LOGGER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+}
+
+static void
+soup_logger_input_stream_pollable_init (GPollableInputStreamInterface *interface,
+ gpointer interface_data)
+{
+ interface->can_poll = soup_logger_input_stream_can_poll;
+ interface->is_readable = soup_logger_input_stream_is_readable;
+ interface->read_nonblocking = soup_logger_input_stream_read_nonblocking;
+ interface->create_source = soup_logger_input_stream_create_source;
+}
+
+SoupLogger *
+soup_logger_input_stream_get_logger (SoupLoggerInputStream *stream)
+{
+ SoupLoggerInputStreamPrivate *priv = soup_logger_input_stream_get_instance_private (stream);
+
+ return priv->logger;
+}
diff --git a/libsoup/soup-logger-input-stream.h b/libsoup/soup-logger-input-stream.h
new file mode 100644
index 00000000..8a3a91d2
--- /dev/null
+++ b/libsoup/soup-logger-input-stream.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2021 Igalia S.L.
+ */
+
+#pragma once
+
+#include "soup-types.h"
+#include "soup-logger.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_LOGGER_INPUT_STREAM (soup_logger_input_stream_get_type ())
+G_DECLARE_FINAL_TYPE (SoupLoggerInputStream,
+ soup_logger_input_stream,
+ SOUP,
+ LOGGER_INPUT_STREAM,
+ GFilterInputStream)
+
+SoupLogger *soup_logger_input_stream_get_logger (SoupLoggerInputStream *stream);
+
+G_END_DECLS
diff --git a/libsoup/soup-logger-private.h b/libsoup/soup-logger-private.h
new file mode 100644
index 00000000..8307319a
--- /dev/null
+++ b/libsoup/soup-logger-private.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2021 Igalia S.L.
+ */
+
+#pragma once
+
+#include "soup-logger.h"
+#include "soup-body-output-stream.h"
+
+G_BEGIN_DECLS
+
+void soup_logger_request_body_setup (SoupLogger *logger, SoupMessage *msg);
+
+G_END_DECLS
diff --git a/libsoup/soup-logger.c b/libsoup/soup-logger.c
index 72afb012..e7a20d71 100644
--- a/libsoup/soup-logger.c
+++ b/libsoup/soup-logger.c
@@ -14,7 +14,8 @@
#include <stdio.h>
#include <string.h>
-#include "soup-logger.h"
+#include "soup-logger-private.h"
+#include "soup-logger-input-stream.h"
#include "soup-connection.h"
#include "soup-message-private.h"
#include "soup-misc.h"
@@ -99,9 +100,13 @@ struct _SoupLogger {
typedef struct {
GQuark tag;
GHashTable *ids;
+ GHashTable *request_bodies;
+ GHashTable *request_messages;
+ GHashTable *response_bodies;
SoupSession *session;
SoupLoggerLogLevel level;
+ int max_body_size;
SoupLoggerFilter request_filter;
gpointer request_filter_data;
@@ -120,16 +125,114 @@ enum {
PROP_0,
PROP_LEVEL,
+ PROP_MAX_BODY_SIZE,
LAST_PROP
};
+static void body_ostream_done (gpointer data, GObject *bostream);
+
static void soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
+static SoupContentProcessorInterface *soup_logger_default_content_processor_interface;
+static void soup_logger_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
+
G_DEFINE_TYPE_WITH_CODE (SoupLogger, soup_logger, G_TYPE_OBJECT,
G_ADD_PRIVATE (SoupLogger)
G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
- soup_logger_session_feature_init))
+ soup_logger_session_feature_init)
+ G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
+ soup_logger_content_processor_init))
+
+static void
+write_body (SoupLogger *logger, const char *buffer, gsize nread,
+ gpointer key, GHashTable *bodies)
+{
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+ GString *body;
+
+ if (!nread)
+ return;
+
+ body = g_hash_table_lookup (bodies, key);
+
+ if (!body) {
+ body = g_string_new (NULL);
+ g_hash_table_insert (bodies, key, body);
+ }
+
+ if (priv->max_body_size > 0) {
+ /* longer than max => we've written the extra [...] */
+ if (body->len > priv->max_body_size)
+ return;
+ int cap = priv->max_body_size - body->len;
+ if (cap)
+ g_string_append_len (body, buffer,
+ (nread < cap) ? nread : cap);
+ if (nread > cap)
+ g_string_append (body, "\n[...]");
+ } else {
+ g_string_append_len (body, buffer, nread);
+ }
+}
+
+static void
+write_response_body (SoupLoggerInputStream *stream, char *buffer, gsize nread,
+ gpointer user_data)
+{
+ SoupLogger *logger = soup_logger_input_stream_get_logger (stream);
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+
+ write_body (logger, buffer, nread, user_data, priv->response_bodies);
+}
+
+static GInputStream *
+soup_logger_content_processor_wrap_input (SoupContentProcessor *processor,
+ GInputStream *base_stream,
+ SoupMessage *msg,
+ GError **error)
+{
+ SoupLogger *logger = SOUP_LOGGER (processor);
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+ SoupLoggerInputStream *stream;
+ SoupLoggerLogLevel log_level;
+
+ if (priv->request_filter)
+ log_level = priv->request_filter (logger, msg,
+ priv->response_filter_data);
+ else
+ log_level = priv->level;
+
+ if (log_level < SOUP_LOGGER_LOG_BODY)
+ return NULL;
+
+ stream = g_object_new (SOUP_TYPE_LOGGER_INPUT_STREAM,
+ "base-stream", base_stream,
+ "logger", logger,
+ NULL);
+
+ g_signal_connect_object (stream, "read-data",
+ G_CALLBACK (write_response_body), msg, 0);
+
+ return G_INPUT_STREAM (stream);
+}
+
+static void
+soup_logger_content_processor_init (SoupContentProcessorInterface *interface,
+ gpointer interface_data)
+{
+ soup_logger_default_content_processor_interface =
+ g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
+
+ interface->processing_stage = SOUP_STAGE_BODY_DATA;
+ interface->wrap_input = soup_logger_content_processor_wrap_input;
+}
+
+static void
+body_free (gpointer str)
+{
+ g_string_free (str, TRUE);
+}
static void
soup_logger_init (SoupLogger *logger)
@@ -141,6 +244,15 @@ soup_logger_init (SoupLogger *logger)
priv->tag = g_quark_from_string (id);
g_free (id);
priv->ids = g_hash_table_new (NULL, NULL);
+ priv->request_bodies = g_hash_table_new_full (NULL, NULL, NULL, body_free);
+ priv->response_bodies = g_hash_table_new_full (NULL, NULL, NULL, body_free);
+ priv->request_messages = g_hash_table_new (NULL, NULL);
+}
+
+static void
+body_ostream_drop_ref (gpointer key, gpointer value, gpointer data)
+{
+ g_object_weak_unref (key, body_ostream_done, data);
}
static void
@@ -149,7 +261,13 @@ soup_logger_finalize (GObject *object)
SoupLogger *logger = SOUP_LOGGER (object);
SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+ g_hash_table_foreach (priv->request_messages,
+ body_ostream_drop_ref, priv);
+
g_hash_table_destroy (priv->ids);
+ g_hash_table_destroy (priv->request_bodies);
+ g_hash_table_destroy (priv->response_bodies);
+ g_hash_table_destroy (priv->request_messages);
if (priv->request_filter_dnotify)
priv->request_filter_dnotify (priv->request_filter_data);
@@ -172,6 +290,9 @@ soup_logger_set_property (GObject *object, guint prop_id,
case PROP_LEVEL:
priv->level = g_value_get_enum (value);
break;
+ case PROP_MAX_BODY_SIZE:
+ priv->max_body_size = g_value_get_int (value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -189,6 +310,9 @@ soup_logger_get_property (GObject *object, guint prop_id,
case PROP_LEVEL:
g_value_set_enum (value, priv->level);
break;
+ case PROP_MAX_BODY_SIZE:
+ g_value_set_int (value, priv->max_body_size);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@@ -220,6 +344,24 @@ soup_logger_class_init (SoupLoggerClass *logger_class)
SOUP_LOGGER_LOG_MINIMAL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
+ /**
+ * SoupLogger:max-body-size:
+ *
+ * If #SoupLogger:level is %SOUP_LOGGER_LOG_BODY, this gives
+ * the maximum number of bytes of the body that will be logged.
+ * (-1 means "no limit".)
+ *
+ **/
+ g_object_class_install_property (
+ object_class, PROP_MAX_BODY_SIZE,
+ g_param_spec_int ("max-body-size",
+ "Max Body Size",
+ "The maximum body size to output",
+ -1,
+ G_MAXINT,
+ -1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
}
/**
@@ -228,6 +370,7 @@ soup_logger_class_init (SoupLoggerClass *logger_class)
* @SOUP_LOGGER_LOG_MINIMAL: Log the Request-Line or Status-Line and
* the Soup-Debug pseudo-headers
* @SOUP_LOGGER_LOG_HEADERS: Log the full request/response headers
+ * @SOUP_LOGGER_LOG_BODY: Log the full headers and request/response bodies
*
* Describes the level of logging output to provide.
**/
@@ -364,6 +507,37 @@ soup_logger_set_printer (SoupLogger *logger,
priv->printer_dnotify = destroy;
}
+/**
+ * soup_logger_set_max_body_size:
+ * @logger: a #SoupLogger
+ * @max_body_size: the maximum body size to log
+ *
+ * Sets the maximum body size for @logger (-1 means no limit).
+ **/
+void
+soup_logger_set_max_body_size (SoupLogger *logger, int max_body_size)
+{
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+
+ priv->max_body_size = max_body_size;
+}
+
+/**
+ * soup_logger_get_max_body_size:
+ * @logger: a #SoupLogger
+ *
+ * Get the maximum body size for @logger.
+ *
+ * Returns: the maximum body size, or -1 if unlimited
+ **/
+int
+soup_logger_get_max_body_size (SoupLogger *logger)
+{
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+
+ return priv->max_body_size;
+}
+
static guint
soup_logger_get_id (SoupLogger *logger, gpointer object)
{
@@ -458,6 +632,7 @@ print_request (SoupLogger *logger, SoupMessage *msg,
SoupMessageHeadersIter iter;
const char *name, *value;
char *socket_dbg;
+ GString *body;
GUri *uri;
if (priv->request_filter) {
@@ -518,6 +693,19 @@ print_request (SoupLogger *logger, SoupMessage *msg,
"%s: %s", name, value);
}
}
+
+ if (log_level == SOUP_LOGGER_LOG_HEADERS)
+ return;
+
+ /* will be logged in get_informational */
+ if (soup_message_headers_get_expectations (soup_message_get_request_headers (msg)) == SOUP_EXPECTATION_CONTINUE)
+ return;
+
+ if (!g_hash_table_steal_extended (priv->request_bodies, msg, NULL, (gpointer *)&body))
+ return;
+
+ soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '>', "\n%s", body->str);
+ g_string_free (body, TRUE);
}
static void
@@ -527,6 +715,7 @@ print_response (SoupLogger *logger, SoupMessage *msg)
SoupLoggerLogLevel log_level;
SoupMessageHeadersIter iter;
const char *name, *value;
+ GString *body;
if (priv->response_filter) {
log_level = priv->response_filter (logger, msg,
@@ -558,6 +747,15 @@ print_response (SoupLogger *logger, SoupMessage *msg)
soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '<',
"%s: %s", name, value);
}
+
+ if (log_level == SOUP_LOGGER_LOG_HEADERS)
+ return;
+
+ if (!g_hash_table_steal_extended (priv->response_bodies, msg, NULL, (gpointer *)&body))
+ return;
+
+ soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, '<', "\n%s", body->str);
+ g_string_free (body, TRUE);
}
static void
@@ -572,17 +770,37 @@ finished (SoupMessage *msg, gpointer user_data)
static void
got_informational (SoupMessage *msg, gpointer user_data)
{
- SoupLogger *logger = user_data;
+ SoupLogger *logger = user_data;
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+ SoupLoggerLogLevel log_level;
+ GString *body = NULL;
- g_signal_handlers_disconnect_by_func (msg, finished, logger);
- print_response (logger, msg);
- soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "\n");
+ if (priv->response_filter)
+ log_level = priv->response_filter (logger, msg,
+ priv->response_filter_data);
+ else
+ log_level = priv->level;
- if (soup_message_get_status (msg) == SOUP_STATUS_CONTINUE && soup_message_get_request_body_stream (msg)) {
- soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
- "[Now sending request body...]");
- soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "\n");
- }
+ g_signal_handlers_disconnect_by_func (msg, finished, logger);
+ print_response (logger, msg);
+ soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "\n");
+
+ if (!g_hash_table_steal_extended (priv->response_bodies, msg, NULL, (gpointer *)&body))
+ return;
+
+ if (soup_message_get_status (msg) == SOUP_STATUS_CONTINUE) {
+ soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, '>',
+ "[Now sending request body...]");
+
+ if (log_level == SOUP_LOGGER_LOG_BODY) {
+ soup_logger_print (logger, SOUP_LOGGER_LOG_BODY,
+ '>', "%s", body->str);
+ }
+
+ soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "\n");
+ }
+
+ g_string_free (body, TRUE);
}
static void
@@ -591,12 +809,45 @@ got_body (SoupMessage *msg, gpointer user_data)
SoupLogger *logger = user_data;
g_signal_handlers_disconnect_by_func (msg, finished, logger);
+
print_response (logger, msg);
soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', "\n");
}
static void
-starting (SoupMessage *msg, gpointer user_data)
+body_stream_wrote_data_cb (GOutputStream *stream, const void *buffer,
+ guint count, SoupLogger *logger)
+{
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+ SoupMessage *msg = g_hash_table_lookup (priv->request_messages,
+ stream);
+
+ write_body (logger, buffer, count, msg, priv->request_bodies);
+}
+
+static void
+body_ostream_done (gpointer data, GObject *bostream)
+{
+ SoupLoggerPrivate *priv = data;
+
+ g_hash_table_remove (priv->request_messages, bostream);
+}
+
+void
+soup_logger_request_body_setup (SoupLogger *logger, SoupMessage *msg)
+{
+ SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
+ SoupMessageIOData *io = (SoupMessageIOData *)soup_message_get_io_data (msg);
+
+ g_hash_table_insert (priv->request_messages, io->body_ostream, msg);
+ g_signal_connect_object (io->body_ostream, "wrote-data",
+ G_CALLBACK (body_stream_wrote_data_cb),
+ logger, 0);
+ g_object_weak_ref (G_OBJECT (io->body_ostream), body_ostream_done, priv);
+}
+
+static void
+wrote_body (SoupMessage *msg, gpointer user_data)
{
SoupLogger *logger = SOUP_LOGGER (user_data);
SoupLoggerPrivate *priv = soup_logger_get_instance_private (logger);
@@ -631,8 +882,8 @@ soup_logger_request_queued (SoupSessionFeature *logger,
{
g_return_if_fail (SOUP_IS_MESSAGE (msg));
- g_signal_connect_after (msg, "starting",
- G_CALLBACK (starting),
+ g_signal_connect (msg, "wrote-body",
+ G_CALLBACK (wrote_body),
logger);
g_signal_connect (msg, "got-informational",
G_CALLBACK (got_informational),
diff --git a/libsoup/soup-logger.h b/libsoup/soup-logger.h
index 7f0defba..03aa00e2 100644
--- a/libsoup/soup-logger.h
+++ b/libsoup/soup-logger.h
@@ -16,7 +16,8 @@ G_DECLARE_FINAL_TYPE (SoupLogger, soup_logger, SOUP, LOGGER, GObject)
typedef enum {
SOUP_LOGGER_LOG_NONE,
SOUP_LOGGER_LOG_MINIMAL,
- SOUP_LOGGER_LOG_HEADERS
+ SOUP_LOGGER_LOG_HEADERS,
+ SOUP_LOGGER_LOG_BODY
} SoupLoggerLogLevel;
typedef SoupLoggerLogLevel (*SoupLoggerFilter) (SoupLogger *logger,
@@ -50,4 +51,11 @@ void soup_logger_set_printer (SoupLogger *logger,
gpointer printer_data,
GDestroyNotify destroy);
+SOUP_AVAILABLE_IN_ALL
+void soup_logger_set_max_body_size (SoupLogger *logger,
+ int max_body_size);
+
+SOUP_AVAILABLE_IN_ALL
+int soup_logger_get_max_body_size (SoupLogger *logger);
+
G_END_DECLS
diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c
index c4999df0..3b4d361c 100644
--- a/libsoup/soup-message-io.c
+++ b/libsoup/soup-message-io.c
@@ -23,6 +23,7 @@
#include "soup-content-processor.h"
#include "content-sniffer/soup-content-sniffer-stream.h"
#include "soup-filter-input-stream.h"
+#include "soup-logger-private.h"
#include "soup-message-private.h"
#include "soup-message-queue-item.h"
#include "soup-misc.h"
@@ -162,9 +163,10 @@ soup_message_setup_body_istream (GInputStream *body_stream,
static void
request_body_stream_wrote_data_cb (SoupMessage *msg,
- guint count)
+ const void *buffer,
+ guint count)
{
- soup_message_wrote_body_data (msg, count);
+ soup_message_wrote_body_data (msg, count);
}
static void
@@ -315,6 +317,7 @@ io_write (SoupMessage *msg, gboolean blocking,
{
SoupClientMessageIOData *client_io = soup_message_get_io_data (msg);
SoupMessageIOData *io = &client_io->base;
+ SoupSessionFeature *logger;
gssize nwrote;
if (io->async_error) {
@@ -365,6 +368,10 @@ io_write (SoupMessage *msg, gboolean blocking,
io->write_encoding,
io->write_length);
io->write_state = SOUP_MESSAGE_IO_STATE_BODY;
+ logger = soup_session_get_feature_for_message (client_io->item->session,
+ SOUP_TYPE_LOGGER, msg);
+ if (logger)
+ soup_logger_request_body_setup (SOUP_LOGGER (logger), msg);
break;
case SOUP_MESSAGE_IO_STATE_BODY:
diff --git a/tests/logger-test.c b/tests/logger-test.c
index b8d882ea..19145b21 100644
--- a/tests/logger-test.c
+++ b/tests/logger-test.c
@@ -4,11 +4,22 @@
#include "test-utils.h"
+static const char body_data[] =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
+ "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
+ "im ad minim veniam, quis nostrud exercitation ullamco laboris "
+ "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in "
+ "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "
+ "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in "
+ "culpa qui officia deserunt mollit anim id est laborum.";
+
GUri *base_uri;
typedef struct {
GHashTable *request;
GHashTable *response;
+ GByteArray *request_body;
+ GByteArray *response_body;
} LogData;
static void
@@ -16,6 +27,8 @@ log_data_clear (LogData *data)
{
g_clear_pointer (&data->request, g_hash_table_destroy);
g_clear_pointer (&data->response, g_hash_table_destroy);
+ g_clear_pointer (&data->request_body, g_byte_array_unref);
+ g_clear_pointer (&data->response_body, g_byte_array_unref);
}
static void
@@ -26,17 +39,29 @@ printer (SoupLogger *logger,
LogData *log)
{
GHashTable **table;
+ GByteArray **body;
- if (direction == '>')
+ if (direction == '>') {
table = &log->request;
- else if (direction == '<')
+ body = &log->request_body;
+ } else if (direction == '<') {
table = &log->response;
- else
+ body = &log->response_body;
+ } else
return;
if (!*table) {
+ /* first logger call; initialize headers */
*table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_insert (*table, g_strdup ("status-line"), g_strdup (data));
+ } else if (!*data) {
+ /* empty line with direction delimits body */
+ *body = g_byte_array_new ();
+ } else if (*body) {
+ /* we're only reading body now */
+ if (!strcmp (data, "[...]"))
+ return;
+ g_byte_array_append (*body, (const unsigned char *)data, strlen (data));
} else {
char *p;
@@ -51,7 +76,7 @@ do_logger_minimal_test (void)
SoupSession *session;
SoupLogger *logger;
SoupMessage *msg;
- LogData log = { NULL, NULL };
+ LogData log = { NULL, NULL, NULL, NULL };
session = soup_test_session_new (NULL);
@@ -78,6 +103,9 @@ do_logger_minimal_test (void)
g_assert_true (g_hash_table_contains (log.response, "Soup-Debug"));
g_assert_cmpstr (g_hash_table_lookup (log.response, "status-line"), ==, "HTTP/1.1 200 OK");
+ g_assert_null (log.request_body);
+ g_assert_null (log.response_body);
+
log_data_clear (&log);
soup_test_session_abort_unref (session);
@@ -90,7 +118,7 @@ do_logger_headers_test (void)
SoupLogger *logger;
SoupMessage *msg;
char *host;
- LogData log = { NULL, NULL };
+ LogData log = { NULL, NULL, NULL, NULL };
session = soup_test_session_new (NULL);
@@ -128,10 +156,120 @@ do_logger_headers_test (void)
g_assert_true (g_hash_table_contains (log.response, "Content-Length"));
g_assert_cmpstr (g_hash_table_lookup (log.response, "status-line"), ==, "HTTP/1.1 200 OK");
g_assert_cmpstr (g_hash_table_lookup (log.response, "Content-Type"), ==, "text/plain");
- g_assert_cmpstr (g_hash_table_lookup (log.response, "Content-Length"), ==, "5");
+ g_assert_cmpint (atoi (g_hash_table_lookup (log.response, "Content-Length")), ==, sizeof (body_data) - 1);
+
+ g_assert_null (log.request_body);
+ g_assert_null (log.response_body);
+
+ log_data_clear (&log);
+
+ soup_test_session_abort_unref (session);
+}
+
+static void
+do_logger_body_test (void)
+{
+ GInputStream *body;
+ GBytes *request;
+ SoupSession *session;
+ SoupLogger *logger;
+ SoupMessage *msg;
+ char *host;
+ LogData log = { NULL, NULL, NULL, NULL };
+
+ session = soup_test_session_new (NULL);
+
+ logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
+ soup_logger_set_printer (logger, (SoupLoggerPrinter)printer, &log, NULL);
+ soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
+
+ msg = soup_message_new_from_uri ("GET", base_uri);
+
+ request = g_bytes_new_static (body_data, sizeof (body_data) - 1);
+ soup_message_set_request_body_from_bytes (msg, NULL, request);
+ g_bytes_unref (request);
+
+ body = soup_session_send (session, msg, NULL, NULL);
+ g_assert_nonnull (body);
+
+ for (;;) {
+ gssize skip = g_input_stream_skip (body, 32, NULL, NULL);
+ if (skip <= 0)
+ break;
+ }
+
+ g_object_unref (body);
+ g_object_unref (msg);
+
+ g_assert_nonnull (log.request);
+ g_assert_cmpuint (g_hash_table_size (log.request), ==, 7);
+ g_assert_true (g_hash_table_contains (log.request, "status-line"));
+ g_assert_true (g_hash_table_contains (log.request, "Soup-Debug-Timestamp"));
+ g_assert_true (g_hash_table_contains (log.request, "Soup-Debug"));
+ g_assert_true (g_hash_table_contains (log.request, "Host"));
+ g_assert_true (g_hash_table_contains (log.request, "Accept-Encoding"));
+ g_assert_true (g_hash_table_contains (log.request, "Connection"));
+ g_assert_cmpstr (g_hash_table_lookup (log.request, "status-line"), ==, "GET / HTTP/1.1");
+ host = g_strdup_printf ("127.0.0.1:%d", g_uri_get_port (base_uri));
+ g_assert_cmpstr (g_hash_table_lookup (log.request, "Host"), ==, host);
+ g_free (host);
+ g_assert_cmpstr (g_hash_table_lookup (log.request, "Accept-Encoding"), ==, "gzip, deflate");
+ g_assert_cmpstr (g_hash_table_lookup (log.request, "Connection"), ==, "Keep-Alive");
+ g_assert_cmpint (atoi (g_hash_table_lookup (log.request, "Content-Length")), ==, sizeof (body_data) - 1);
+
+ g_assert_nonnull (log.response);
+ g_assert_cmpuint (g_hash_table_size (log.response), ==, 6);
+ g_assert_true (g_hash_table_contains (log.response, "status-line"));
+ g_assert_true (g_hash_table_contains (log.response, "Soup-Debug-Timestamp"));
+ g_assert_true (g_hash_table_contains (log.response, "Soup-Debug"));
+ g_assert_true (g_hash_table_contains (log.response, "Date"));
+ g_assert_true (g_hash_table_contains (log.response, "Content-Type"));
+ g_assert_true (g_hash_table_contains (log.response, "Content-Length"));
+ g_assert_cmpstr (g_hash_table_lookup (log.response, "status-line"), ==, "HTTP/1.1 200 OK");
+ g_assert_cmpstr (g_hash_table_lookup (log.response, "Content-Type"), ==, "text/plain");
+ g_assert_cmpint (atoi (g_hash_table_lookup (log.response, "Content-Length")), ==, sizeof (body_data) - 1);
+
+ g_assert_nonnull (log.request_body);
+ g_assert_nonnull (log.response_body);
+
+ g_assert_cmpmem (log.request_body->data,
+ log.request_body->len,
+ body_data, sizeof (body_data) - 1);
+
+ g_assert_cmpmem (log.response_body->data,
+ log.response_body->len,
+ body_data, sizeof (body_data) - 1);
log_data_clear (&log);
+ /* restrict maximum body size */
+ soup_logger_set_max_body_size (logger, 64);
+
+ msg = soup_message_new_from_uri ("GET", base_uri);
+ body = soup_session_send (session, msg, NULL, NULL);
+ g_assert_nonnull (body);
+
+ for (;;) {
+ gssize skip = g_input_stream_skip (body, 32, NULL, NULL);
+ if (skip <= 0)
+ break;
+ }
+
+ g_object_unref (body);
+ g_object_unref (msg);
+
+ g_assert_nonnull (log.request);
+ g_assert_null (log.request_body);
+ g_assert_nonnull (log.response);
+ g_assert_nonnull (log.response_body);
+
+ g_assert_cmpmem (log.response_body->data,
+ log.response_body->len,
+ body_data, 64);
+
+ log_data_clear (&log);
+
+ g_object_unref (logger);
soup_test_session_abort_unref (session);
}
@@ -146,14 +284,15 @@ filter (SoupLogger *logger,
static void
do_logger_filters_test (void)
{
+ GInputStream *body;
SoupSession *session;
SoupLogger *logger;
SoupMessage *msg;
- LogData log = { NULL, NULL };
+ LogData log = { NULL, NULL, NULL, NULL };
session = soup_test_session_new (NULL);
- logger = soup_logger_new (SOUP_LOGGER_LOG_HEADERS);
+ logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
soup_logger_set_printer (logger, (SoupLoggerPrinter)printer, &log, NULL);
soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
@@ -168,6 +307,9 @@ do_logger_filters_test (void)
g_assert_nonnull (log.request);
g_assert_cmpuint (g_hash_table_size (log.request), ==, 3);
g_assert_null (log.response);
+ g_assert_null (log.request_body);
+ g_assert_null (log.response_body);
+
log_data_clear (&log);
/* Log request with headers level and response with minimal */
@@ -182,6 +324,9 @@ do_logger_filters_test (void)
g_assert_cmpuint (g_hash_table_size (log.request), ==, 6);
g_assert_nonnull (log.response);
g_assert_cmpuint (g_hash_table_size (log.response), ==, 3);
+ g_assert_null (log.request_body);
+ g_assert_null (log.response_body);
+
log_data_clear (&log);
/* Only log response with headers level */
@@ -195,6 +340,39 @@ do_logger_filters_test (void)
g_assert_null (log.request);
g_assert_nonnull (log.response);
g_assert_cmpuint (g_hash_table_size (log.response), ==, 6);
+ g_assert_null (log.request_body);
+ g_assert_null (log.response_body);
+
+ log_data_clear (&log);
+
+ /* Log request with minimal and response with body */
+ soup_logger_set_request_filter (logger, filter, GUINT_TO_POINTER (SOUP_LOGGER_LOG_MINIMAL), NULL);
+ soup_logger_set_response_filter (logger, filter, GUINT_TO_POINTER (SOUP_LOGGER_LOG_BODY), NULL);
+
+ msg = soup_message_new_from_uri ("GET", base_uri);
+ body = soup_session_send (session, msg, NULL, NULL);
+ g_assert_nonnull (body);
+
+ for (;;) {
+ gssize skip = g_input_stream_skip (body, 32, NULL, NULL);
+ if (skip <= 0)
+ break;
+ }
+
+ g_object_unref (body);
+ g_object_unref (msg);
+
+ g_assert_nonnull (log.request);
+ g_assert_null (log.request_body);
+ g_assert_cmpuint (g_hash_table_size (log.request), ==, 3);
+ g_assert_nonnull (log.response);
+ g_assert_cmpuint (g_hash_table_size (log.response), ==, 6);
+ g_assert_nonnull (log.response_body);
+
+ g_assert_cmpmem (log.response_body->data,
+ log.response_body->len,
+ body_data, sizeof (body_data) - 1);
+
log_data_clear (&log);
g_object_unref (logger);
@@ -208,7 +386,7 @@ do_logger_cookies_test (void)
SoupLogger *logger;
GUri *uri;
SoupMessage *msg;
- LogData log = { NULL, NULL };
+ LogData log = { NULL, NULL, NULL, NULL };
session = soup_test_session_new (NULL);
soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR);
@@ -260,7 +438,9 @@ server_callback (SoupServer *server,
}
soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
soup_server_message_set_response (msg, "text/plain",
- SOUP_MEMORY_STATIC, "index", 5);
+ SOUP_MEMORY_STATIC,
+ body_data,
+ sizeof (body_data) - 1);
}
int
@@ -277,6 +457,7 @@ main (int argc, char **argv)
g_test_add_func ("/logger/minimal", do_logger_minimal_test);
g_test_add_func ("/logger/headers", do_logger_headers_test);
+ g_test_add_func ("/logger/body", do_logger_body_test);
g_test_add_func ("/logger/filters", do_logger_filters_test);
g_test_add_func ("/logger/cookies", do_logger_cookies_test);
diff --git a/tests/test-utils.c b/tests/test-utils.c
index 88247a98..21ece494 100644
--- a/tests/test-utils.c
+++ b/tests/test-utils.c
@@ -289,7 +289,7 @@ soup_test_session_new (const char *propname, ...)
}
if (http_debug_level && !logger) {
- SoupLoggerLogLevel level = MIN ((SoupLoggerLogLevel)http_debug_level, SOUP_LOGGER_LOG_HEADERS);
+ SoupLoggerLogLevel level = MIN ((SoupLoggerLogLevel)http_debug_level, SOUP_LOGGER_LOG_BODY);
logger = soup_logger_new (level);
}