summaryrefslogtreecommitdiff
path: root/libsoup/server/soup-server-message.c
diff options
context:
space:
mode:
authorCarlos Garcia Campos <cgarcia@igalia.com>2020-10-09 11:29:11 +0200
committerCarlos Garcia Campos <cgarcia@igalia.com>2020-10-19 14:02:25 +0200
commit99c19cc27ae837e665ace3c1f0e99cd1088e6c24 (patch)
treebbb7f7a7a9c9544801c66cf3e543ee3ea93291be /libsoup/server/soup-server-message.c
parentd5cd7249b20beee01dc26c09ec80f270ef8962fd (diff)
downloadlibsoup-carlosgc/split-io.tar.gz
Split SoupMessage into client and server partscarlosgc/split-io
Add SoupServerMessage and move there all the server only functionality.
Diffstat (limited to 'libsoup/server/soup-server-message.c')
-rw-r--r--libsoup/server/soup-server-message.c881
1 files changed, 881 insertions, 0 deletions
diff --git a/libsoup/server/soup-server-message.c b/libsoup/server/soup-server-message.c
new file mode 100644
index 00000000..1f2e8515
--- /dev/null
+++ b/libsoup/server/soup-server-message.c
@@ -0,0 +1,881 @@
+/*
+ * soup-server-message.c: HTTP server request/response
+ *
+ * Copyright (C) 2020 Igalia S.L.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-server-message.h"
+#include "soup.h"
+#include "soup-connection.h"
+#include "soup-server-message-private.h"
+#include "soup-socket-private.h"
+
+/**
+ * SECTION:soup-server-message
+ * @short_description: An HTTP server request and response.
+ * @see_also: #SoupMessageHeaders, #SoupMessageBody
+ *
+ * A SoupServerMessage represents an HTTP message that is being sent or
+ * received on a #SoupServer
+ *
+ * #SoupServer will create #SoupServerMessage<!-- -->s automatically for
+ * incoming requests, which your application will receive via handlers.
+ *
+ * Note that libsoup's terminology here does not quite match the HTTP
+ * specification: in RFC 2616, an "HTTP-message" is
+ * <emphasis>either</emphasis> a Request, <emphasis>or</emphasis> a
+ * Response. In libsoup, a #SoupServerMessage combines both the request and
+ * the response.
+ **/
+
+struct _SoupServerMessage {
+ GObject parent;
+
+ SoupSocket *sock;
+ GSocket *gsock;
+ SoupAuthDomain *auth_domain;
+ char *auth_user;
+
+ GSocketAddress *remote_addr;
+ char *remote_ip;
+ GSocketAddress *local_addr;
+
+ const char *method;
+ SoupHTTPVersion http_version;
+ SoupHTTPVersion orig_http_version;
+
+ guint status_code;
+ char *reason_phrase;
+
+ SoupURI *uri;
+
+ SoupMessageBody *request_body;
+ SoupMessageHeaders *request_headers;
+
+ SoupMessageBody *response_body;
+ SoupMessageHeaders *response_headers;
+
+ SoupServerMessageIOData *io_data;
+};
+
+struct _SoupServerMessageClass {
+ GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (SoupServerMessage, soup_server_message, G_TYPE_OBJECT)
+
+enum {
+ WROTE_INFORMATIONAL,
+ WROTE_HEADERS,
+ WROTE_CHUNK,
+ WROTE_BODY_DATA,
+ WROTE_BODY,
+
+ GOT_HEADERS,
+ GOT_CHUNK,
+ GOT_BODY,
+
+ DISCONNECTED,
+ FINISHED,
+
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static void
+soup_server_message_init (SoupServerMessage *msg)
+{
+ msg->request_body = soup_message_body_new ();
+ msg->request_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
+ msg->response_body = soup_message_body_new ();
+ msg->response_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
+ soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CONTENT_LENGTH);
+}
+
+static void
+soup_server_message_finalize (GObject *object)
+{
+ SoupServerMessage *msg = SOUP_SERVER_MESSAGE (object);
+
+ soup_server_message_io_data_free (msg->io_data);
+
+ g_clear_object (&msg->auth_domain);
+ g_clear_pointer (&msg->auth_user, g_free);
+ g_clear_object (&msg->remote_addr);
+ g_clear_object (&msg->local_addr);
+
+ if (msg->sock) {
+ g_signal_handlers_disconnect_by_data (msg->sock, msg);
+ g_object_unref (msg->sock);
+ }
+ g_clear_object (&msg->gsock);
+ g_clear_pointer (&msg->remote_ip, g_free);
+
+ g_clear_pointer (&msg->uri, soup_uri_free);
+ g_free (msg->reason_phrase);
+
+ soup_message_body_free (msg->request_body);
+ soup_message_headers_free (msg->request_headers);
+ soup_message_body_free (msg->response_body);
+ soup_message_headers_free (msg->response_headers);
+
+ G_OBJECT_CLASS (soup_server_message_parent_class)->finalize (object);
+}
+
+static void
+soup_server_message_class_init (SoupServerMessageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = soup_server_message_finalize;
+
+ /**
+ * SoupServerMessage::wrote-informational:
+ * @msg: the message
+ *
+ * Emitted immediately after writing a 1xx (Informational) response.
+ */
+ signals[WROTE_INFORMATIONAL] =
+ g_signal_new ("wrote-informational",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * SoupServerMessage::wrote-headers:
+ * @msg: the message
+ *
+ * Emitted immediately after writing the response headers for a
+ * message.
+ */
+ signals[WROTE_HEADERS] =
+ g_signal_new ("wrote-headers",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * SoupServerMessage::wrote-chunk:
+ * @msg: the message
+ *
+ * Emitted immediately after writing a body chunk for a message.
+ *
+ * Note that this signal is not parallel to
+ * #SoupServerMessage::got-chunk; it is emitted only when a complete
+ * chunk (added with soup_message_body_append() or
+ * soup_message_body_append_bytes()) has been written. To get
+ * more useful continuous progress information, use
+ * #SoupServerMessage::wrote-body-data.
+ */
+ signals[WROTE_CHUNK] =
+ g_signal_new ("wrote-chunk",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * SoupServerMessage::wrote-body-data:
+ * @msg: the message
+ * @chunk_size: the number of bytes written
+ *
+ * Emitted immediately after writing a portion of the message
+ * body to the network.
+ */
+ signals[WROTE_BODY_DATA] =
+ g_signal_new ("wrote-body-data",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_UINT);
+
+ /**
+ * SoupServerMessage::wrote-body:
+ * @msg: the message
+ *
+ * Emitted immediately after writing the complete response body for a
+ * message.
+ */
+ signals[WROTE_BODY] =
+ g_signal_new ("wrote-body",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * SoupServerMessage::got-headers:
+ * @msg: the message
+ *
+ * Emitted after receiving the Request-Line and request headers.
+ */
+ signals[GOT_HEADERS] =
+ g_signal_new ("got-headers",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * SoupServerMessage::got-chunk:
+ * @msg: the message
+ * @chunk: the just-read chunk
+ *
+ * Emitted after receiving a chunk of a message body. Note
+ * that "chunk" in this context means any subpiece of the
+ * body, not necessarily the specific HTTP 1.1 chunks sent by
+ * the other side.
+ */
+ signals[GOT_CHUNK] =
+ g_signal_new ("got-chunk",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_BYTES);
+
+ /**
+ * SoupServerMessage::got-body:
+ * @msg: the message
+ *
+ * Emitted after receiving the complete request body.
+ */
+ signals[GOT_BODY] =
+ g_signal_new ("got-body",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * SoupServerMessage::finished:
+ * @msg: the message
+ *
+ * Emitted when all HTTP processing is finished for a message.
+ * (After #SoupServerMessage::wrote-body).
+ */
+ signals[FINISHED] =
+ g_signal_new ("finished",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ /**
+ * SoupServerMessage::disconnected:
+ * @msg: the message
+ *
+ * Emitted when the @msg's socket is disconnected.
+ */
+ signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+socket_disconnected (SoupServerMessage *msg)
+{
+ g_signal_emit (msg, signals[DISCONNECTED], 0);
+}
+
+SoupServerMessage *
+soup_server_message_new (SoupSocket *sock)
+{
+ SoupServerMessage *msg;
+
+ msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL);
+ msg->sock = g_object_ref (sock);
+ msg->gsock = soup_socket_get_gsocket (sock);
+ if (msg->gsock)
+ g_object_ref (msg->gsock);
+
+ g_signal_connect_object (sock, "disconnected",
+ G_CALLBACK (socket_disconnected),
+ msg, G_CONNECT_SWAPPED);
+
+ return msg;
+}
+
+void
+soup_server_message_set_uri (SoupServerMessage *msg,
+ SoupURI *uri)
+{
+ if (msg->uri)
+ soup_uri_free (msg->uri);
+ msg->uri = soup_uri_copy (uri);
+}
+
+SoupSocket *
+soup_server_message_get_soup_socket (SoupServerMessage *msg)
+{
+ return msg->sock;
+}
+
+void
+soup_server_message_set_auth (SoupServerMessage *msg,
+ SoupAuthDomain *domain,
+ char *user)
+{
+ if (msg->auth_domain)
+ g_object_unref (msg->auth_domain);
+ msg->auth_domain = domain;
+
+ if (msg->auth_user)
+ g_free (msg->auth_user);
+ msg->auth_user = user;
+}
+
+gboolean
+soup_server_message_is_keepalive (SoupServerMessage *msg)
+{
+ if (msg->status_code == SOUP_STATUS_OK && msg->method == SOUP_METHOD_CONNECT)
+ return TRUE;
+
+ /* Not persistent if the server sent a terminate-by-EOF response */
+ if (soup_message_headers_get_encoding (msg->response_headers) == SOUP_ENCODING_EOF)
+ return FALSE;
+
+ if (msg->http_version == SOUP_HTTP_1_0) {
+ /* In theory, HTTP/1.0 connections are only persistent
+ * if the client requests it, and the server agrees.
+ * But some servers do keep-alive even if the client
+ * doesn't request it. So ignore c_conn.
+ */
+
+ if (!soup_message_headers_header_contains (msg->response_headers,
+ "Connection", "Keep-Alive"))
+ return FALSE;
+ } else {
+ /* Normally persistent unless either side requested otherwise */
+ if (soup_message_headers_header_contains (msg->request_headers,
+ "Connection", "close") ||
+ soup_message_headers_header_contains (msg->response_headers,
+ "Connection", "close"))
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+void
+soup_server_message_set_io_data (SoupServerMessage *msg,
+ SoupServerMessageIOData *io)
+{
+ soup_server_message_io_data_free (msg->io_data);
+ msg->io_data = io;
+}
+
+SoupServerMessageIOData *
+soup_server_message_get_io_data (SoupServerMessage *msg)
+{
+ return msg->io_data;
+}
+
+void
+soup_server_message_cleanup_response (SoupServerMessage *msg)
+{
+ soup_message_body_truncate (msg->response_body);
+ soup_message_headers_clear (msg->response_headers);
+ soup_message_headers_set_encoding (msg->response_headers,
+ SOUP_ENCODING_CONTENT_LENGTH);
+ msg->status_code = SOUP_STATUS_NONE;
+ g_clear_pointer (&msg->reason_phrase, g_free);
+ msg->http_version = msg->orig_http_version;
+}
+
+void
+soup_server_message_wrote_informational (SoupServerMessage *msg)
+{
+ g_signal_emit (msg, signals[WROTE_INFORMATIONAL], 0);
+}
+
+void
+soup_server_message_wrote_headers (SoupServerMessage *msg)
+{
+ g_signal_emit (msg, signals[WROTE_HEADERS], 0);
+}
+
+void
+soup_server_message_wrote_chunk (SoupServerMessage *msg)
+{
+ g_signal_emit (msg, signals[WROTE_CHUNK], 0);
+}
+
+void
+soup_server_message_wrote_body_data (SoupServerMessage *msg,
+ gsize chunk_size)
+{
+ g_signal_emit (msg, signals[WROTE_BODY_DATA], 0, chunk_size);
+}
+
+void
+soup_server_message_wrote_body (SoupServerMessage *msg)
+{
+ g_signal_emit (msg, signals[WROTE_BODY], 0);
+}
+
+void
+soup_server_message_got_headers (SoupServerMessage *msg)
+{
+ g_signal_emit (msg, signals[GOT_HEADERS], 0);
+}
+
+void
+soup_server_message_got_chunk (SoupServerMessage *msg,
+ GBytes *chunk)
+{
+ g_signal_emit (msg, signals[GOT_CHUNK], 0, chunk);
+}
+
+void
+soup_server_message_got_body (SoupServerMessage *msg)
+{
+ if (soup_message_body_get_accumulate (msg->request_body))
+ g_bytes_unref (soup_message_body_flatten (msg->request_body));
+ g_signal_emit (msg, signals[GOT_BODY], 0);
+}
+
+void
+soup_server_message_finished (SoupServerMessage *msg)
+{
+ g_signal_emit (msg, signals[FINISHED], 0);
+}
+
+/**
+ * soup_server_message_get_request_headers:
+ * @msg: a #SoupServerMessage
+ *
+ * Get the request headers of @msg.
+ *
+ * Returns: (transfer none): a #SoupMessageHeaders with the request headers.
+ */
+SoupMessageHeaders *
+soup_server_message_get_request_headers (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ return msg->request_headers;
+}
+
+/**
+ * soup_server_message_get_response_headers:
+ * @msg: a #SoupServerMessage
+ *
+ * Get the response headers of @msg.
+ *
+ * Returns: (transfer none): a #SoupMessageHeaders with the response headers.
+ */
+SoupMessageHeaders *
+soup_server_message_get_response_headers (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ return msg->response_headers;
+}
+
+/**
+ * soup_server_message_get_request_body:
+ * @msg: a #SoupServerMessage
+ *
+ * Get the request body of @msg.
+ *
+ * Returns: (transfer none): a #SoupMessageBody.
+ */
+SoupMessageBody *
+soup_server_message_get_request_body (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ return msg->request_body;
+}
+
+/**
+ * soup_server_message_get_response_body:
+ * @msg: a #SoupServerMessage
+ *
+ * Get the response body of @msg.
+ *
+ * Returns: (transfer none): a #SoupMessageBody.
+ */
+SoupMessageBody *
+soup_server_message_get_response_body (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ return msg->response_body;
+}
+
+/**
+ * soup_server_message_get_method:
+ * @msg: a #SoupServerMessage
+ *
+ * Get the HTTP method of @msg.
+ *
+ * Returns: the HTTP method.
+ */
+const char *
+soup_server_message_get_method (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ return msg->method;
+}
+
+void
+soup_server_message_set_method (SoupServerMessage *msg,
+ const char *method)
+{
+ msg->method = g_intern_string (method);
+}
+
+/**
+ * soup_server_message_get_http_version:
+ * @msg: a #SoupServerMessage
+ *
+ * Get the HTTP version of @msg.
+ *
+ * Returns: a #SoupHTTPVersion.
+ */
+SoupHTTPVersion
+soup_server_message_get_http_version (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), SOUP_HTTP_1_1);
+
+ return msg->http_version;
+}
+
+/**
+ * soup_server_message_set_http_version:
+ * @msg: a #SoupServerMessage
+ * @version: a #SoupHTTPVersion
+ *
+ * Set the HTTP version of @msg.
+ */
+void
+soup_server_message_set_http_version (SoupServerMessage *msg,
+ SoupHTTPVersion version)
+{
+ g_return_if_fail (SOUP_IS_SERVER_MESSAGE (msg));
+
+ msg->http_version = version;
+ if (msg->status_code == SOUP_STATUS_NONE)
+ msg->orig_http_version = version;
+}
+
+/**
+ * soup_server_message_get_status:
+ * @msg: a #SoupServerMessage
+ * @reason_phrase: (out) (nullable) (transfer none): a location to store the reason phrase or %NULL
+ *
+ * Get the HTTP status code of @msg and optionally the reason phrase if @reason_phrase is not %NULL.
+ *
+ * Returns: the HTTP status code.
+ */
+guint
+soup_server_message_get_status (SoupServerMessage *msg,
+ const char **reason_phrase)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), 0);
+
+ if (reason_phrase)
+ *reason_phrase = msg->reason_phrase;
+
+ return msg->status_code;
+}
+
+/**
+ * soup_server_message_set_status:
+ * @msg: a #SoupServerMessage
+ * @status_code: an HTTP status code
+ * @reason_phrase: (nullable): a reason phrase
+ *
+ * Sets @msg's status code to @status_code. If @status_code is a
+ * known value and @reason_phrase is %NULL, the reason_phrase will
+ * be set automatically.
+ **/
+void
+soup_server_message_set_status (SoupServerMessage *msg,
+ guint status_code,
+ const char *reason_phrase)
+{
+ g_return_if_fail (SOUP_IS_SERVER_MESSAGE (msg));
+ g_return_if_fail (status_code != 0);
+
+ g_free (msg->reason_phrase);
+
+ msg->status_code = status_code;
+ msg->reason_phrase = g_strdup (reason_phrase ? reason_phrase : soup_status_get_phrase (status_code));
+}
+
+/**
+ * soup_server_message_get_uri:
+ * @msg: a #SoupServerMessage
+ *
+ * Get @msg's URI.
+ *
+ * Returns: (transfer none): a #SoupURI
+ */
+SoupURI *
+soup_server_message_get_uri (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ return msg->uri;
+}
+
+/**
+ * soup_server_message_set_response:
+ * @msg: the message
+ * @content_type: (allow-none): MIME Content-Type of the body
+ * @resp_use: a #SoupMemoryUse describing how to handle @resp_body
+ * @resp_body: (allow-none) (array length=resp_length) (element-type guint8):
+ * a data buffer containing the body of the message response.
+ * @resp_length: the byte length of @resp_body.
+ *
+ * Convenience function to set the response body of a #SoupServerMessage. If
+ * @content_type is %NULL, the response body must be empty as well.
+ */
+void
+soup_server_message_set_response (SoupServerMessage *msg,
+ const char *content_type,
+ SoupMemoryUse resp_use,
+ const char *resp_body,
+ gsize resp_length)
+{
+ g_return_if_fail (SOUP_IS_SERVER_MESSAGE (msg));
+ g_return_if_fail (content_type != NULL || resp_length == 0);
+
+ if (content_type) {
+ g_warn_if_fail (strchr (content_type, '/') != NULL);
+
+ soup_message_headers_replace (msg->response_headers,
+ "Content-Type", content_type);
+ soup_message_body_append (msg->response_body, resp_use,
+ resp_body, resp_length);
+ } else {
+ soup_message_headers_remove (msg->response_headers,
+ "Content-Type");
+ soup_message_body_truncate (msg->response_body);
+ }
+}
+
+/**
+ * soup_server_message_set_redirect:
+ * @msg: a #SoupServerMessage
+ * @status_code: a 3xx status code
+ * @redirect_uri: the URI to redirect @msg to
+ *
+ * Sets @msg's status_code to @status_code and adds a Location header
+ * pointing to @redirect_uri. Use this from a #SoupServer when you
+ * want to redirect the client to another URI.
+ *
+ * @redirect_uri can be a relative URI, in which case it is
+ * interpreted relative to @msg's current URI. In particular, if
+ * @redirect_uri is just a path, it will replace the path
+ * <emphasis>and query</emphasis> of @msg's URI.
+ */
+void
+soup_server_message_set_redirect (SoupServerMessage *msg,
+ guint status_code,
+ const char *redirect_uri)
+{
+ SoupURI *location;
+ char *location_str;
+
+ g_return_if_fail (SOUP_IS_SERVER_MESSAGE (msg));
+
+ location = soup_uri_new_with_base (soup_server_message_get_uri (msg), redirect_uri);
+ g_return_if_fail (location != NULL);
+
+ soup_server_message_set_status (msg, status_code, NULL);
+ location_str = soup_uri_to_string (location, FALSE);
+ soup_message_headers_replace (msg->response_headers, "Location",
+ location_str);
+ g_free (location_str);
+ soup_uri_free (location);
+}
+
+/**
+ * soup_server_message_get_socket:
+ * @msg: a #SoupServerMessage
+ *
+ * Retrieves the #GSocket that @msg is associated with.
+ *
+ * If you are using this method to observe when multiple requests are
+ * made on the same persistent HTTP connection (eg, as the ntlm-test
+ * test program does), you will need to pay attention to socket
+ * destruction as well (eg, by using weak references), so that you do
+ * not get fooled when the allocator reuses the memory address of a
+ * previously-destroyed socket to represent a new socket.
+ *
+ * Return value: (nullable) (transfer none): the #GSocket that @msg is
+ * associated with, %NULL if you used soup_server_accept_iostream().
+ */
+GSocket *
+soup_server_message_get_socket (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ return msg->gsock;
+}
+
+/**
+ * soup_server_message_get_remote_address:
+ * @msg: a #SoupServerMessage
+ *
+ * Retrieves the #GSocketAddress associated with the remote end
+ * of a connection.
+ *
+ * Return value: (nullable) (transfer none): the #GSocketAddress
+ * associated with the remote end of a connection, it may be
+ * %NULL if you used soup_server_accept_iostream().
+ */
+GSocketAddress *
+soup_server_message_get_remote_address (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ if (msg->remote_addr)
+ return msg->remote_addr;
+
+ msg->remote_addr = msg->gsock ?
+ g_socket_get_remote_address (msg->gsock, NULL) :
+ G_SOCKET_ADDRESS (g_object_ref (soup_socket_get_remote_address (msg->sock)));
+
+ return msg->remote_addr;
+}
+
+/**
+ * soup_server_message_get_local_address:
+ * @msg: a #SoupServerMessage
+ *
+ * Retrieves the #GSocketAddress associated with the local end
+ * of a connection.
+ *
+ * Return value: (nullable) (transfer none): the #GSocketAddress
+ * associated with the local end of a connection, it may be
+ * %NULL if you used soup_server_accept_iostream().
+ */
+GSocketAddress *
+soup_server_message_get_local_address (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ if (msg->local_addr)
+ return msg->local_addr;
+
+ msg->local_addr = msg->gsock ?
+ g_socket_get_local_address (msg->gsock, NULL) :
+ G_SOCKET_ADDRESS (g_object_ref (soup_socket_get_local_address (msg->sock)));
+
+ return msg->local_addr;
+}
+
+/**
+ * soup_server_message_get_remote_host:
+ * @msg: a #SoupServerMessage
+ *
+ * Retrieves the IP address associated with the remote end of a
+ * connection.
+ *
+ * Return value: (nullable): the IP address associated with the remote
+ * end of a connection, it may be %NULL if you used
+ * soup_server_accept_iostream().
+ */
+const char *
+soup_server_message_get_remote_host (SoupServerMessage *msg)
+{
+ g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);
+
+ if (msg->remote_ip)
+ return msg->remote_ip;
+
+ if (msg->gsock) {
+ GSocketAddress *addr = soup_server_message_get_remote_address (msg);
+ GInetAddress *iaddr;
+
+ if (!addr || !G_IS_INET_SOCKET_ADDRESS (addr))
+ return NULL;
+ iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr));
+ msg->remote_ip = g_inet_address_to_string (iaddr);
+ } else {
+ GInetSocketAddress *addr = G_INET_SOCKET_ADDRESS (soup_socket_get_remote_address (msg->sock));
+ GInetAddress *inet_addr = g_inet_socket_address_get_address (addr);
+ msg->remote_ip = g_inet_address_to_string (inet_addr);
+ }
+
+ return msg->remote_ip;
+}
+
+/**
+ * soup_server_message_steal_connection:
+ * @msg: a #SoupServerMessage
+ *
+ * "Steals" the HTTP connection associated with @msg from its
+ * #SoupServer. This happens immediately, regardless of the current
+ * state of the connection; if the response to @msg has not yet finished
+ * being sent, then it will be discarded; you can steal the connection from a
+ * #SoupServerMessage:wrote-informational or #SoupServerMessage:wrote-body signal
+ * handler if you need to wait for part or all of the response to be sent.
+ *
+ * Note that when calling this function from C, @msg will most
+ * likely be freed as a side effect.
+ *
+ * Return value: (transfer full): the #GIOStream formerly associated
+ * with @msg (or %NULL if @msg was no longer associated with a
+ * connection). No guarantees are made about what kind of #GIOStream
+ * is returned.
+ */
+GIOStream *
+soup_server_message_steal_connection (SoupServerMessage *msg)
+{
+ GIOStream *stream;
+
+ g_object_ref (msg);
+ stream = soup_server_message_io_steal (msg);
+ if (stream) {
+ g_object_set_data_full (G_OBJECT (stream), "GSocket",
+ soup_socket_steal_gsocket (msg->sock),
+ g_object_unref);
+ }
+
+ socket_disconnected (msg);
+ g_object_unref (msg);
+
+ return stream;
+}