diff options
Diffstat (limited to 'libsoup/server/soup-server-message.c')
-rw-r--r-- | libsoup/server/soup-server-message.c | 881 |
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; +} |