/*
 * 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-message-headers-private.h"
#include "soup-uri-utils-private.h"

/**
 * SoupServerMessage:
 *
 * An HTTP server request and response pair.
 *
 * A SoupServerMessage represents an HTTP message that is being sent or
 * received on a [class@Server].
 *
 * [class@Server] 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 *either* a Request, *or* a
 * Response. In libsoup, a #SoupServerMessage combines both the request and the
 * response.
 **/

struct _SoupServerMessage {
        GObject             parent;

        SoupServerConnection *conn;
        SoupAuthDomain     *auth_domain;
        char               *auth_user;

        char               *remote_ip;

        const char         *method;
        SoupHTTPVersion     http_version;
        SoupHTTPVersion     orig_http_version;

        guint               status_code;
        char               *reason_phrase;

        GUri               *uri;

        SoupMessageBody    *request_body;
        SoupMessageHeaders *request_headers;

        SoupMessageBody    *response_body;
        SoupMessageHeaders *response_headers;

        SoupServerMessageIO *io_data;

        gboolean                 options_ping;

        GTlsCertificate      *tls_peer_certificate;
        GTlsCertificateFlags  tls_peer_certificate_errors;
};

struct _SoupServerMessageClass {
        GObjectClass parent_class;
};

G_DEFINE_FINAL_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,

        CONNECTED,
        DISCONNECTED,
        FINISHED,

        ACCEPT_CERTIFICATE,

        LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

enum {
	PROP_0,

	PROP_TLS_PEER_CERTIFICATE,
	PROP_TLS_PEER_CERTIFICATE_ERRORS,

	LAST_PROPERTY
};

static GParamSpec *properties[LAST_PROPERTY] = { NULL, };

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);

        g_clear_object (&msg->auth_domain);
        g_clear_pointer (&msg->auth_user, g_free);

        if (msg->conn) {
                g_signal_handlers_disconnect_by_data (msg->conn, msg);
                g_object_unref (msg->conn);
        }
        g_clear_pointer (&msg->remote_ip, g_free);

        g_clear_pointer (&msg->uri, g_uri_unref);
        g_free (msg->reason_phrase);

        soup_message_body_unref (msg->request_body);
        soup_message_headers_unref (msg->request_headers);
        soup_message_body_unref (msg->response_body);
        soup_message_headers_unref (msg->response_headers);

        G_OBJECT_CLASS (soup_server_message_parent_class)->finalize (object);
}

static void
soup_server_message_get_property (GObject *object, guint prop_id,
                                  GValue *value, GParamSpec *pspec)
{
	SoupServerMessage *msg = SOUP_SERVER_MESSAGE (object);

	switch (prop_id) {
	case PROP_TLS_PEER_CERTIFICATE:
		g_value_set_object (value, msg->tls_peer_certificate);
		break;
	case PROP_TLS_PEER_CERTIFICATE_ERRORS:
		g_value_set_flags (value, msg->tls_peer_certificate_errors);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
soup_server_message_class_init (SoupServerMessageClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);

        object_class->finalize = soup_server_message_finalize;
        object_class->get_property = soup_server_message_get_property;

        /**
         * 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
         * [signal@ServerMessage::got-chunk]; it is emitted only when a complete
         * chunk (added with [method@MessageBody.append] or
         * [method@MessageBody.append_bytes] has been written. To get
         * more useful continuous progress information, use
         * [signal@ServerMessage::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 [signal@ServerMessage::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::connected:
         * @msg: the message
         *
         * Emitted when the @msg's socket is connected and the TLS handshake completed.
         */
        signals[CONNECTED] =
                g_signal_new ("connected",
                              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);

	/**
	 * SoupServerMessage::accept-certificate:
	 * @msg: the message
	 * @tls_peer_certificate: the peer's #GTlsCertificate
	 * @tls_peer_errors: the tls errors of @tls_certificate
	 *
	 * Emitted during the @msg's connection TLS handshake
	 * after client TLS certificate has been received.
	 * You can return %TRUE to accept @tls_certificate despite
	 * @tls_errors.
	 *
	 * Returns: %TRUE to accept the TLS certificate and stop other
	 *   handlers from being invoked, or %FALSE to propagate the
	 *   event further.
	 */
	signals[ACCEPT_CERTIFICATE] =
		g_signal_new ("accept-certificate",
			      G_OBJECT_CLASS_TYPE (object_class),
			      G_SIGNAL_RUN_LAST,
			      0,
			      g_signal_accumulator_true_handled, NULL,
			      NULL,
			      G_TYPE_BOOLEAN, 2,
			      G_TYPE_TLS_CERTIFICATE,
			      G_TYPE_TLS_CERTIFICATE_FLAGS);

	/**
	 * SoupServerMessage:tls-peer-certificate:
	 *
	 * The peer's #GTlsCertificate associated with the message
	 *
	 * Since: 3.2
	 */
        properties[PROP_TLS_PEER_CERTIFICATE] =
		g_param_spec_object ("tls-peer-certificate",
				     "TLS Peer Certificate",
				     "The TLS peer certificate associated with the message",
				     G_TYPE_TLS_CERTIFICATE,
				     G_PARAM_READABLE |
				     G_PARAM_STATIC_STRINGS);

	/**
	 * SoupServerMessage:tls-peer-certificate-errors:
	 *
	 * The verification errors on #SoupServerMessage:tls-peer-certificate
	 *
	 * Since: 3.2
	 */
        properties[PROP_TLS_PEER_CERTIFICATE_ERRORS] =
		g_param_spec_flags ("tls-peer-certificate-errors",
				    "TLS Peer Certificate Errors",
				    "The verification errors on the message's TLS peer certificate",
				    G_TYPE_TLS_CERTIFICATE_FLAGS, 0,
				    G_PARAM_READABLE |
				    G_PARAM_STATIC_STRINGS);

        g_object_class_install_properties (object_class, LAST_PROPERTY, properties);
}

static void
connection_connected (SoupServerMessage *msg)
{
        g_assert (!msg->io_data);
        msg->io_data = soup_server_connection_get_io_data (msg->conn);
        g_signal_emit (msg, signals[CONNECTED], 0);
}

static void
connection_disconnected (SoupServerMessage *msg)
{
        msg->io_data = NULL;
        g_signal_emit (msg, signals[DISCONNECTED], 0);
}

static gboolean
connection_accept_certificate (SoupServerMessage    *msg,
                               GTlsCertificate      *tls_certificate,
                               GTlsCertificateFlags *tls_errors)
{
	gboolean accept = FALSE;

	g_signal_emit (msg, signals[ACCEPT_CERTIFICATE], 0,
		       tls_certificate, tls_errors, &accept);
	return accept;
}

static void
soup_server_message_set_tls_peer_certificate (SoupServerMessage   *msg,
                                              GTlsCertificate     *tls_certificate,
                                              GTlsCertificateFlags tls_errors)
{
        if (msg->tls_peer_certificate == tls_certificate && msg->tls_peer_certificate_errors == tls_errors)
                return;

        g_clear_object (&msg->tls_peer_certificate);
        msg->tls_peer_certificate = tls_certificate ? g_object_ref (tls_certificate) : NULL;
        msg->tls_peer_certificate_errors = tls_errors;
        g_object_notify_by_pspec (G_OBJECT (msg), properties[PROP_TLS_PEER_CERTIFICATE]);
        g_object_notify_by_pspec (G_OBJECT (msg), properties[PROP_TLS_PEER_CERTIFICATE_ERRORS]);
}

static void
re_emit_tls_certificate_changed (SoupServerMessage    *msg,
                                 GParamSpec           *pspec,
                                 SoupServerConnection *conn)
{
        soup_server_message_set_tls_peer_certificate (msg,
                                                      soup_server_connection_get_tls_peer_certificate (conn),
                                                      soup_server_connection_get_tls_peer_certificate_errors (conn));
}

SoupServerMessage *
soup_server_message_new (SoupServerConnection *conn)
{
        SoupServerMessage *msg;

        msg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL);
        msg->conn = g_object_ref (conn);
        msg->io_data = soup_server_connection_get_io_data (msg->conn);

        g_signal_connect_object (conn, "connected",
                                 G_CALLBACK (connection_connected),
                                 msg, G_CONNECT_SWAPPED);
        g_signal_connect_object (conn, "disconnected",
                                 G_CALLBACK (connection_disconnected),
                                 msg, G_CONNECT_SWAPPED);
        g_signal_connect_object (conn, "accept-certificate",
                                 G_CALLBACK (connection_accept_certificate),
                                 msg, G_CONNECT_SWAPPED);
        g_signal_connect_object (conn, "notify::tls-certificate",
                                 G_CALLBACK (re_emit_tls_certificate_changed),
                                 msg, G_CONNECT_SWAPPED);

        return msg;
}

void
soup_server_message_set_uri (SoupServerMessage *msg,
                             GUri              *uri)
{
        if (msg->uri)
                g_uri_unref (msg->uri);
        msg->uri = soup_uri_copy_with_normalized_flags (uri);
}

SoupServerConnection *
soup_server_message_get_connection (SoupServerMessage *msg)
{
        return msg->conn;
}

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->http_version == SOUP_HTTP_2_0)
                return TRUE;

        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_common (msg->response_headers,
                                                                  SOUP_HEADER_CONNECTION,
                                                                  "Keep-Alive"))
                        return FALSE;
        } else {
                /* Normally persistent unless either side requested otherwise */
                if (soup_message_headers_header_contains_common (msg->request_headers,
                                                                 SOUP_HEADER_CONNECTION,
                                                                 "close") ||
                    soup_message_headers_header_contains_common (msg->response_headers,
                                                                 SOUP_HEADER_CONNECTION,
                                                                 "close"))
                        return FALSE;

                return TRUE;
        }

        return TRUE;
}

void
soup_server_message_read_request (SoupServerMessage        *msg,
                                  SoupMessageIOCompletionFn completion_cb,
                                  gpointer                  user_data)
{
        soup_server_message_io_read_request (msg->io_data, msg, completion_cb, user_data);
}

SoupServerMessageIO *
soup_server_message_get_io_data (SoupServerMessage *msg)
{
        return msg->io_data;
}

/**
 * soup_server_message_pause:
 * @msg: a SoupServerMessage
 *
 * Pauses I/O on @msg.
 *
 * This can be used when you need to return from the server handler without
 * having the full response ready yet. Use [method@ServerMessage.unpause] to
 * resume I/O.
 *
 * Since: 3.2
 */
void
soup_server_message_pause (SoupServerMessage *msg)
{
        g_return_if_fail (SOUP_IS_SERVER_MESSAGE (msg));
        g_return_if_fail (msg->io_data != NULL);

        soup_server_message_io_pause (msg->io_data, msg);
}

/**
 * soup_server_message_unpause:
 * @msg: a SoupServerMessage
 *
 * Resumes I/O on @msg.
 *
 * Use this to resume after calling [method@ServerMessage.pause], or after
 * adding a new chunk to a chunked response. I/O won't actually resume until you
 * return to the main loop.
 *
 * Since: 3.2
 */
void
soup_server_message_unpause (SoupServerMessage *msg)
{
        g_return_if_fail (SOUP_IS_SERVER_MESSAGE (msg));
        g_return_if_fail (msg->io_data != NULL);

        soup_server_message_io_unpause (msg->io_data, msg);
}

gboolean
soup_server_message_is_io_paused (SoupServerMessage *msg)
{
        return msg->io_data && soup_server_message_io_is_paused (msg->io_data, msg);
}

void
soup_server_message_finish (SoupServerMessage *msg)
{
        if (!msg->io_data)
                return;

        soup_server_message_io_finished (g_steal_pointer (&msg->io_data), msg);
}

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);
}

void
soup_server_message_set_options_ping (SoupServerMessage *msg,
                                      gboolean           is_options_ping)
{
        msg->options_ping = is_options_ping;
}

/**
 * soup_server_message_is_options_ping:
 * @msg: a #SoupServerMessage
 *
 * Gets if @msg represents an OPTIONS message with the path `*`.
 * 
 * Returns: %TRUE if is an OPTIONS ping
 */
gboolean
soup_server_message_is_options_ping (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), FALSE);

        return msg->options_ping;
}

/**
 * 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_reason_phrase:
 * @msg: a #SoupServerMessage:
 *
 * Get the HTTP reason phrase of @msg.
 *
 * Returns: (nullable): the reason phrase.
 */
const char *
soup_server_message_get_reason_phrase (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);

        return msg->reason_phrase;
}

/**
 * soup_server_message_get_status:
 * @msg: a #SoupServerMessage
 *
 * Get the HTTP status code of @msg.
 *
 * Returns: the HTTP status code.
 */
guint
soup_server_message_get_status (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), 0);

        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 #GUri
 */
GUri *
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: (nullable): MIME Content-Type of the body
 * @resp_use: a #SoupMemoryUse describing how to handle @resp_body
 * @resp_body: (nullable) (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_common (msg->response_headers,
                                                     SOUP_HEADER_CONTENT_TYPE,
                                                     content_type);
                soup_message_body_append (msg->response_body, resp_use,
                                          resp_body, resp_length);
        } else {
                soup_message_headers_remove_common (msg->response_headers,
                                                    SOUP_HEADER_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 [class@Server] 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
 * *and query* of @msg's URI.
 */
void
soup_server_message_set_redirect (SoupServerMessage *msg,
                                  guint              status_code,
                                  const char        *redirect_uri)
{
	GUri *location;
	char *location_str;

        g_return_if_fail (SOUP_IS_SERVER_MESSAGE (msg));

	location = g_uri_parse_relative (soup_server_message_get_uri (msg), redirect_uri, SOUP_HTTP_URI_FLAGS, NULL);
	g_return_if_fail (location != NULL);

	soup_server_message_set_status (msg, status_code, NULL);
	location_str = g_uri_to_string (location);
	soup_message_headers_replace_common (msg->response_headers, SOUP_HEADER_LOCATION,
                                             location_str);
	g_free (location_str);
	g_uri_unref (location);
}

/**
 * soup_server_message_get_socket:
 * @msg: a #SoupServerMessage
 *
 * Retrieves the [class@Gio.Socket] 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.
 *
 * Returns: (nullable) (transfer none): the #GSocket that @msg is
 *   associated with, %NULL if you used [method@Server.accept_iostream].
 */
GSocket *
soup_server_message_get_socket (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);

        return soup_server_connection_get_socket (msg->conn);
}

/**
 * soup_server_message_get_remote_address:
 * @msg: a #SoupServerMessage
 *
 * Retrieves the [class@Gio.SocketAddress] associated with the remote end
 * of a connection.
 *
 * Returns: (nullable) (transfer none): the #GSocketAddress
 *   associated with the remote end of a connection, it may be
 *   %NULL if you used [class@Server.accept_iostream].
 */
GSocketAddress *
soup_server_message_get_remote_address (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);

        return soup_server_connection_get_remote_address (msg->conn);
}

/**
 * soup_server_message_get_local_address:
 * @msg: a #SoupServerMessage
 *
 * Retrieves the [class@Gio.SocketAddress] associated with the local end
 * of a connection.
 *
 * Returns: (nullable) (transfer none): the #GSocketAddress
 *   associated with the local end of a connection, it may be
 *   %NULL if you used [method@Server.accept_iostream].
 */
GSocketAddress *
soup_server_message_get_local_address (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);

        return soup_server_connection_get_local_address (msg->conn);
}

/**
 * soup_server_message_get_remote_host:
 * @msg: a #SoupServerMessage
 *
 * Retrieves the IP address associated with the remote end of a
 * connection.
 *
 * Returns: (nullable): the IP address associated with the remote
 *   end of a connection, it may be %NULL if you used
 *   [method@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) {
                GSocketAddress *addr = soup_server_connection_get_remote_address (msg->conn);
                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);
        }

        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
 * [signal@ServerMessage::wrote-informational] or
 * [signal@ServerMessage::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.
 *
 * Returns: (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_connection_steal (msg->conn);
        g_signal_handlers_disconnect_by_data (msg, msg->conn);
        g_object_unref (msg);

        return stream;
}

/**
 * soup_server_message_get_tls_peer_certificate:
 * @msg: a #SoupMessage
 *
 * Gets the peer's #GTlsCertificate associated with @msg's connection.
 * Note that this is not set yet during the emission of
 * SoupServerMessage::accept-certificate signal.
 *
 * Returns: (transfer none) (nullable): @msg's TLS peer certificate,
 *    or %NULL if @msg's connection is not SSL.
 *
 * Since: 3.2
 */
GTlsCertificate *
soup_server_message_get_tls_peer_certificate (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), NULL);

        return msg->tls_peer_certificate;
}

/**
 * soup_server_message_get_tls_peer_certificate_errors:
 * @msg: a #SoupMessage
 *
 * Gets the errors associated with validating @msg's TLS peer certificate.
 * Note that this is not set yet during the emission of
 * SoupServerMessage::accept-certificate signal.
 *
 * Returns: a #GTlsCertificateFlags with @msg's TLS peer certificate errors.
 *
 * Since: 3.2
 */
GTlsCertificateFlags
soup_server_message_get_tls_peer_certificate_errors (SoupServerMessage *msg)
{
        g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (msg), 0);

        return msg->tls_peer_certificate_errors;
}
