summaryrefslogtreecommitdiff
path: root/libsoup/server
diff options
context:
space:
mode:
authorCarlos Garcia Campos <cgarcia@igalia.com>2020-10-07 14:03:17 +0200
committerCarlos Garcia Campos <cgarcia@igalia.com>2020-10-09 11:34:08 +0200
commit869e580033ec92cc8782f941da04d623b481e3e4 (patch)
tree657cb4fdd4d7cb055a78d43593ffeab1b427da67 /libsoup/server
parent6c2ef5ae4fad12caddb1ff4f07bb2f4d2af9386e (diff)
downloadlibsoup-869e580033ec92cc8782f941da04d623b481e3e4.tar.gz
server: move SoupServer code to server directory
Diffstat (limited to 'libsoup/server')
-rw-r--r--libsoup/server/soup-auth-domain-basic.c348
-rw-r--r--libsoup/server/soup-auth-domain-basic.h35
-rw-r--r--libsoup/server/soup-auth-domain-digest.c454
-rw-r--r--libsoup/server/soup-auth-domain-digest.h39
-rw-r--r--libsoup/server/soup-auth-domain.c635
-rw-r--r--libsoup/server/soup-auth-domain.h92
-rw-r--r--libsoup/server/soup-message-server-io.c304
-rw-r--r--libsoup/server/soup-path-map.c192
-rw-r--r--libsoup/server/soup-path-map.h26
-rw-r--r--libsoup/server/soup-server-private.h23
-rw-r--r--libsoup/server/soup-server.c2364
-rw-r--r--libsoup/server/soup-server.h180
12 files changed, 4692 insertions, 0 deletions
diff --git a/libsoup/server/soup-auth-domain-basic.c b/libsoup/server/soup-auth-domain-basic.c
new file mode 100644
index 00000000..9a850050
--- /dev/null
+++ b/libsoup/server/soup-auth-domain-basic.c
@@ -0,0 +1,348 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-domain-basic.c: HTTP Basic Authentication (server-side)
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-domain-basic.h"
+#include "soup.h"
+
+/**
+ * SECTION:soup-auth-domain-basic
+ * @short_description: Server-side "Basic" authentication
+ *
+ * #SoupAuthDomainBasic handles the server side of HTTP "Basic" (ie,
+ * cleartext password) authentication.
+ **/
+
+enum {
+ PROP_0,
+
+ PROP_AUTH_CALLBACK,
+ PROP_AUTH_DATA,
+
+ LAST_PROP
+};
+
+struct _SoupAuthDomainBasic {
+ SoupAuthDomain parent;
+};
+
+typedef struct {
+ SoupAuthDomainBasicAuthCallback auth_callback;
+ gpointer auth_data;
+ GDestroyNotify auth_dnotify;
+} SoupAuthDomainBasicPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthDomainBasic, soup_auth_domain_basic, SOUP_TYPE_AUTH_DOMAIN)
+
+static void
+soup_auth_domain_basic_init (SoupAuthDomainBasic *basic)
+{
+}
+
+static void
+soup_auth_domain_basic_finalize (GObject *object)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ G_OBJECT_CLASS (soup_auth_domain_basic_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_domain_basic_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ priv->auth_callback = g_value_get_pointer (value);
+ break;
+ case PROP_AUTH_DATA:
+ if (priv->auth_dnotify) {
+ priv->auth_dnotify (priv->auth_data);
+ priv->auth_dnotify = NULL;
+ }
+ priv->auth_data = g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_basic_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ g_value_set_pointer (value, priv->auth_callback);
+ break;
+ case PROP_AUTH_DATA:
+ g_value_set_pointer (value, priv->auth_data);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * soup_auth_domain_basic_new:
+ * @optname1: name of first option, or %NULL
+ * @...: option name/value pairs
+ *
+ * Creates a #SoupAuthDomainBasic. You must set the
+ * %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be
+ * returned with the authentication challenge to the client. Other
+ * parameters are optional.
+ *
+ * Return value: the new #SoupAuthDomain
+ **/
+SoupAuthDomain *
+soup_auth_domain_basic_new (const char *optname1, ...)
+{
+ SoupAuthDomain *domain;
+ va_list ap;
+
+ va_start (ap, optname1);
+ domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_BASIC,
+ optname1, ap);
+ va_end (ap);
+
+ g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
+
+ return domain;
+}
+
+/**
+ * SoupAuthDomainBasicAuthCallback:
+ * @domain: (type SoupAuthDomainBasic): the domain
+ * @msg: the message being authenticated
+ * @username: the username provided by the client
+ * @password: the password provided by the client
+ * @user_data: the data passed to soup_auth_domain_basic_set_auth_callback()
+ *
+ * Callback used by #SoupAuthDomainBasic for authentication purposes.
+ * The application should verify that @username and @password and valid
+ * and return %TRUE or %FALSE.
+ *
+ * If you are maintaining your own password database (rather than
+ * using the password to authenticate against some other system like
+ * PAM or a remote server), you should make sure you know what you are
+ * doing. In particular, don't store cleartext passwords, or
+ * easily-computed hashes of cleartext passwords, even if you don't
+ * care that much about the security of your server, because users
+ * will frequently use the same password for multiple sites, and so
+ * compromising any site with a cleartext (or easily-cracked) password
+ * database may give attackers access to other more-interesting sites
+ * as well.
+ *
+ * Return value: %TRUE if @username and @password are valid
+ **/
+
+/**
+ * soup_auth_domain_basic_set_auth_callback:
+ * @domain: (type SoupAuthDomainBasic): the domain
+ * @callback: the callback
+ * @user_data: data to pass to @auth_callback
+ * @dnotify: destroy notifier to free @user_data when @domain
+ * is destroyed
+ *
+ * Sets the callback that @domain will use to authenticate incoming
+ * requests. For each request containing authorization, @domain will
+ * invoke the callback, and then either accept or reject the request
+ * based on @callback's return value.
+ *
+ * You can also set the auth callback by setting the
+ * %SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK and
+ * %SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA properties, which can also be
+ * used to set the callback at construct time.
+ **/
+void
+soup_auth_domain_basic_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainBasicAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (domain));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ priv->auth_callback = callback;
+ priv->auth_data = user_data;
+ priv->auth_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA);
+}
+
+static void
+pw_free (char *pw)
+{
+ memset (pw, 0, strlen (pw));
+ g_free (pw);
+}
+
+static gboolean
+parse_basic (SoupMessage *msg, const char *header,
+ char **username, char **password)
+{
+ char *decoded, *colon;
+ gsize len, plen;
+
+ if (!header || (strncmp (header, "Basic ", 6) != 0))
+ return FALSE;
+
+ decoded = (char *)g_base64_decode (header + 6, &len);
+ if (!decoded)
+ return FALSE;
+
+ colon = memchr (decoded, ':', len);
+ if (!colon) {
+ pw_free (decoded);
+ return FALSE;
+ }
+ *colon = '\0';
+ plen = len - (colon - decoded) - 1;
+
+ *password = g_strndup (colon + 1, plen);
+ memset (colon + 1, 0, plen);
+ *username = decoded;
+ return TRUE;
+}
+
+static char *
+soup_auth_domain_basic_accepts (SoupAuthDomain *domain, SoupMessage *msg,
+ const char *header)
+{
+ SoupAuthDomainBasicPrivate *priv =
+ soup_auth_domain_basic_get_instance_private (SOUP_AUTH_DOMAIN_BASIC (domain));
+ char *username, *password;
+ gboolean ok = FALSE;
+
+ if (!parse_basic (msg, header, &username, &password))
+ return NULL;
+
+ if (priv->auth_callback) {
+ ok = priv->auth_callback (domain, msg, username, password,
+ priv->auth_data);
+ } else {
+ ok = soup_auth_domain_try_generic_auth_callback (
+ domain, msg, username);
+ }
+
+ pw_free (password);
+
+ if (ok)
+ return username;
+ else {
+ g_free (username);
+ return NULL;
+ }
+}
+
+static char *
+soup_auth_domain_basic_challenge (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ GString *challenge;
+
+ challenge = g_string_new ("Basic ");
+ soup_header_g_string_append_param (challenge, "realm", soup_auth_domain_get_realm (domain));
+ return g_string_free (challenge, FALSE);
+}
+
+static gboolean
+soup_auth_domain_basic_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password)
+{
+ const char *header;
+ char *msg_username, *msg_password;
+ gboolean ok;
+
+ header = soup_message_headers_get_one (msg->request_headers,
+ "Authorization");
+ if (!parse_basic (msg, header, &msg_username, &msg_password))
+ return FALSE;
+
+ ok = (!strcmp (username, msg_username) &&
+ !strcmp (password, msg_password));
+ g_free (msg_username);
+ pw_free (msg_password);
+
+ return ok;
+}
+
+static void
+soup_auth_domain_basic_class_init (SoupAuthDomainBasicClass *basic_class)
+{
+ SoupAuthDomainClass *auth_domain_class =
+ SOUP_AUTH_DOMAIN_CLASS (basic_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (basic_class);
+
+ auth_domain_class->accepts = soup_auth_domain_basic_accepts;
+ auth_domain_class->challenge = soup_auth_domain_basic_challenge;
+ auth_domain_class->check_password = soup_auth_domain_basic_check_password;
+
+ object_class->finalize = soup_auth_domain_basic_finalize;
+ object_class->set_property = soup_auth_domain_basic_set_property;
+ object_class->get_property = soup_auth_domain_basic_get_property;
+
+ /**
+ * SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK:
+ *
+ * Alias for the #SoupAuthDomainBasic:auth-callback property.
+ * (The #SoupAuthDomainBasicAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainBasic:auth-callback: (type SoupAuthDomainBasicAuthCallback)
+ *
+ * The #SoupAuthDomainBasicAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_CALLBACK,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK,
+ "Authentication callback",
+ "Password-checking callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA:
+ *
+ * Alias for the #SoupAuthDomainBasic:auth-data property.
+ * (The data to pass to the #SoupAuthDomainBasicAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainBasic:auth-data:
+ *
+ * The data to pass to the #SoupAuthDomainBasicAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA,
+ "Authentication callback data",
+ "Data to pass to authentication callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
diff --git a/libsoup/server/soup-auth-domain-basic.h b/libsoup/server/soup-auth-domain-basic.h
new file mode 100644
index 00000000..a260e891
--- /dev/null
+++ b/libsoup/server/soup-auth-domain-basic.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#pragma once
+
+#include "soup-auth-domain.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH_DOMAIN_BASIC (soup_auth_domain_basic_get_type ())
+SOUP_AVAILABLE_IN_2_4
+G_DECLARE_FINAL_TYPE (SoupAuthDomainBasic, soup_auth_domain_basic, SOUP, AUTH_DOMAIN_BASIC, SoupAuthDomain)
+
+#define SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK "auth-callback"
+#define SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA "auth-data"
+
+SOUP_AVAILABLE_IN_2_4
+SoupAuthDomain *soup_auth_domain_basic_new (const char *optname1,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef gboolean (*SoupAuthDomainBasicAuthCallback) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password,
+ gpointer user_data);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_basic_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainBasicAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify);
+
+G_END_DECLS
diff --git a/libsoup/server/soup-auth-domain-digest.c b/libsoup/server/soup-auth-domain-digest.c
new file mode 100644
index 00000000..ae9fa266
--- /dev/null
+++ b/libsoup/server/soup-auth-domain-digest.c
@@ -0,0 +1,454 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-domain-digest.c: HTTP Digest Authentication (server-side)
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "soup-auth-domain-digest.h"
+#include "soup.h"
+#include "auth/soup-auth-digest-private.h"
+
+/**
+ * SECTION:soup-auth-domain-digest
+ * @short_description: Server-side "Digest" authentication
+ *
+ * #SoupAuthDomainDigest handles the server side of HTTP "Digest"
+ * authentication.
+ **/
+
+enum {
+ PROP_0,
+
+ PROP_AUTH_CALLBACK,
+ PROP_AUTH_DATA,
+
+ LAST_PROP
+};
+
+struct _SoupAuthDomainDigest {
+ SoupAuthDomain parent;
+};
+
+typedef struct {
+ SoupAuthDomainDigestAuthCallback auth_callback;
+ gpointer auth_data;
+ GDestroyNotify auth_dnotify;
+
+} SoupAuthDomainDigestPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP_TYPE_AUTH_DOMAIN)
+
+static void
+soup_auth_domain_digest_init (SoupAuthDomainDigest *digest)
+{
+}
+
+static void
+soup_auth_domain_digest_finalize (GObject *object)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ G_OBJECT_CLASS (soup_auth_domain_digest_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_domain_digest_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ priv->auth_callback = g_value_get_pointer (value);
+ break;
+ case PROP_AUTH_DATA:
+ if (priv->auth_dnotify) {
+ priv->auth_dnotify (priv->auth_data);
+ priv->auth_dnotify = NULL;
+ }
+ priv->auth_data = g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_digest_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (object));
+
+ switch (prop_id) {
+ case PROP_AUTH_CALLBACK:
+ g_value_set_pointer (value, priv->auth_callback);
+ break;
+ case PROP_AUTH_DATA:
+ g_value_set_pointer (value, priv->auth_data);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * soup_auth_domain_digest_new:
+ * @optname1: name of first option, or %NULL
+ * @...: option name/value pairs
+ *
+ * Creates a #SoupAuthDomainDigest. You must set the
+ * %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be
+ * returned with the authentication challenge to the client. Other
+ * parameters are optional.
+ *
+ * Return value: the new #SoupAuthDomain
+ **/
+SoupAuthDomain *
+soup_auth_domain_digest_new (const char *optname1, ...)
+{
+ SoupAuthDomain *domain;
+ va_list ap;
+
+ va_start (ap, optname1);
+ domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_DIGEST,
+ optname1, ap);
+ va_end (ap);
+
+ g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL);
+
+ return domain;
+}
+
+/**
+ * SoupAuthDomainDigestAuthCallback:
+ * @domain: (type SoupAuthDomainDigest): the domain
+ * @msg: the message being authenticated
+ * @username: the username provided by the client
+ * @user_data: the data passed to soup_auth_domain_digest_set_auth_callback()
+ *
+ * Callback used by #SoupAuthDomainDigest for authentication purposes.
+ * The application should look up @username in its password database,
+ * and return the corresponding encoded password (see
+ * soup_auth_domain_digest_encode_password()).
+ *
+ * Return value: (nullable): the encoded password, or %NULL if
+ * @username is not a valid user. @domain will free the password when
+ * it is done with it.
+ **/
+
+/**
+ * soup_auth_domain_digest_set_auth_callback:
+ * @domain: (type SoupAuthDomainDigest): the domain
+ * @callback: the callback
+ * @user_data: data to pass to @auth_callback
+ * @dnotify: destroy notifier to free @user_data when @domain
+ * is destroyed
+ *
+ * Sets the callback that @domain will use to authenticate incoming
+ * requests. For each request containing authorization, @domain will
+ * invoke the callback, and then either accept or reject the request
+ * based on @callback's return value.
+ *
+ * You can also set the auth callback by setting the
+ * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK and
+ * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA properties, which can also be
+ * used to set the callback at construct time.
+ **/
+void
+soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainDigestAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (domain));
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ priv->auth_callback = callback;
+ priv->auth_data = user_data;
+ priv->auth_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA);
+}
+
+static gboolean
+check_hex_urp (SoupAuthDomain *domain, SoupMessage *msg,
+ GHashTable *params, const char *username,
+ const char *hex_urp)
+{
+ const char *uri, *qop, *realm, *msg_username;
+ const char *nonce, *nc, *cnonce, *response;
+ char hex_a1[33], computed_response[33];
+ int nonce_count;
+ SoupURI *dig_uri, *req_uri;
+
+ msg_username = g_hash_table_lookup (params, "username");
+ if (!msg_username || strcmp (msg_username, username) != 0)
+ return FALSE;
+
+ /* Check uri */
+ uri = g_hash_table_lookup (params, "uri");
+ if (!uri)
+ return FALSE;
+
+ req_uri = soup_message_get_uri (msg);
+ dig_uri = soup_uri_new (uri);
+ if (dig_uri) {
+ if (!soup_uri_equal (dig_uri, req_uri)) {
+ soup_uri_free (dig_uri);
+ return FALSE;
+ }
+ soup_uri_free (dig_uri);
+ } else {
+ char *req_path;
+ char *dig_path;
+
+ req_path = soup_uri_to_string (req_uri, TRUE);
+ dig_path = soup_uri_decode (uri);
+
+ if (strcmp (dig_path, req_path) != 0) {
+ g_free (req_path);
+ g_free (dig_path);
+ return FALSE;
+ }
+ g_free (req_path);
+ g_free (dig_path);
+ }
+
+ /* Check qop; we only support "auth" for now */
+ qop = g_hash_table_lookup (params, "qop");
+ if (!qop || strcmp (qop, "auth") != 0)
+ return FALSE;
+
+ /* Check realm */
+ realm = g_hash_table_lookup (params, "realm");
+ if (!realm || strcmp (realm, soup_auth_domain_get_realm (domain)) != 0)
+ return FALSE;
+
+ nonce = g_hash_table_lookup (params, "nonce");
+ if (!nonce)
+ return FALSE;
+ nc = g_hash_table_lookup (params, "nc");
+ if (!nc)
+ return FALSE;
+ nonce_count = strtoul (nc, NULL, 16);
+ if (nonce_count <= 0)
+ return FALSE;
+ cnonce = g_hash_table_lookup (params, "cnonce");
+ if (!cnonce)
+ return FALSE;
+ response = g_hash_table_lookup (params, "response");
+ if (!response)
+ return FALSE;
+
+ soup_auth_digest_compute_hex_a1 (hex_urp,
+ SOUP_AUTH_DIGEST_ALGORITHM_MD5,
+ nonce, cnonce, hex_a1);
+ soup_auth_digest_compute_response (msg->method, uri,
+ hex_a1,
+ SOUP_AUTH_DIGEST_QOP_AUTH,
+ nonce, cnonce, nonce_count,
+ computed_response);
+ return strcmp (response, computed_response) == 0;
+}
+
+static char *
+soup_auth_domain_digest_accepts (SoupAuthDomain *domain, SoupMessage *msg,
+ const char *header)
+{
+ SoupAuthDomainDigestPrivate *priv =
+ soup_auth_domain_digest_get_instance_private (SOUP_AUTH_DOMAIN_DIGEST (domain));
+ GHashTable *params;
+ const char *username;
+ gboolean accept = FALSE;
+ char *ret_user;
+
+ if (strncmp (header, "Digest ", 7) != 0)
+ return NULL;
+
+ params = soup_header_parse_param_list (header + 7);
+ if (!params)
+ return NULL;
+
+ username = g_hash_table_lookup (params, "username");
+ if (!username) {
+ soup_header_free_param_list (params);
+ return NULL;
+ }
+
+ if (priv->auth_callback) {
+ char *hex_urp;
+
+ hex_urp = priv->auth_callback (domain, msg, username,
+ priv->auth_data);
+ if (hex_urp) {
+ accept = check_hex_urp (domain, msg, params,
+ username, hex_urp);
+ g_free (hex_urp);
+ } else
+ accept = FALSE;
+ } else {
+ accept = soup_auth_domain_try_generic_auth_callback (
+ domain, msg, username);
+ }
+
+ ret_user = accept ? g_strdup (username) : NULL;
+ soup_header_free_param_list (params);
+ return ret_user;
+}
+
+static char *
+soup_auth_domain_digest_challenge (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ GString *str;
+
+ str = g_string_new ("Digest ");
+ soup_header_g_string_append_param_quoted (str, "realm", soup_auth_domain_get_realm (domain));
+ g_string_append_printf (str, ", nonce=\"%lu%lu\"",
+ (unsigned long) msg,
+ (unsigned long) time (0));
+ g_string_append_printf (str, ", qop=\"auth\"");
+ g_string_append_printf (str, ", algorithm=MD5");
+
+ return g_string_free (str, FALSE);
+}
+
+/**
+ * soup_auth_domain_digest_encode_password:
+ * @username: a username
+ * @realm: an auth realm name
+ * @password: the password for @username in @realm
+ *
+ * Encodes the username/realm/password triplet for Digest
+ * authentication. (That is, it returns a stringified MD5 hash of
+ * @username, @realm, and @password concatenated together). This is
+ * the form that is needed as the return value of
+ * #SoupAuthDomainDigest's auth handler.
+ *
+ * For security reasons, you should store the encoded hash, rather
+ * than storing the cleartext password itself and calling this method
+ * only when you need to verify it. This way, if your server is
+ * compromised, the attackers will not gain access to cleartext
+ * passwords which might also be usable at other sites. (Note also
+ * that the encoded password returned by this method is identical to
+ * the encoded password stored in an Apache .htdigest file.)
+ *
+ * Return value: the encoded password
+ **/
+char *
+soup_auth_domain_digest_encode_password (const char *username,
+ const char *realm,
+ const char *password)
+{
+ char hex_urp[33];
+
+ soup_auth_digest_compute_hex_urp (username, realm, password, hex_urp);
+ return g_strdup (hex_urp);
+}
+
+static gboolean
+soup_auth_domain_digest_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password)
+{
+ const char *header;
+ GHashTable *params;
+ const char *msg_username;
+ char hex_urp[33];
+ gboolean accept;
+
+ header = soup_message_headers_get_one (msg->request_headers,
+ "Authorization");
+ if (!header || (strncmp (header, "Digest ", 7) != 0))
+ return FALSE;
+
+ params = soup_header_parse_param_list (header + 7);
+ if (!params)
+ return FALSE;
+
+ msg_username = g_hash_table_lookup (params, "username");
+ if (!msg_username || strcmp (msg_username, username) != 0) {
+ soup_header_free_param_list (params);
+ return FALSE;
+ }
+
+ soup_auth_digest_compute_hex_urp (username,
+ soup_auth_domain_get_realm (domain),
+ password, hex_urp);
+ accept = check_hex_urp (domain, msg, params, username, hex_urp);
+ soup_header_free_param_list (params);
+ return accept;
+}
+
+static void
+soup_auth_domain_digest_class_init (SoupAuthDomainDigestClass *digest_class)
+{
+ SoupAuthDomainClass *auth_domain_class =
+ SOUP_AUTH_DOMAIN_CLASS (digest_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (digest_class);
+
+ auth_domain_class->accepts = soup_auth_domain_digest_accepts;
+ auth_domain_class->challenge = soup_auth_domain_digest_challenge;
+ auth_domain_class->check_password = soup_auth_domain_digest_check_password;
+
+ object_class->finalize = soup_auth_domain_digest_finalize;
+ object_class->set_property = soup_auth_domain_digest_set_property;
+ object_class->get_property = soup_auth_domain_digest_get_property;
+
+ /**
+ * SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK:
+ *
+ * Alias for the #SoupAuthDomainDigest:auth-callback property.
+ * (The #SoupAuthDomainDigestAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainDigest:auth-callback: (type SoupAuthDomainDigestAuthCallback)
+ *
+ * The #SoupAuthDomainDigestAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_CALLBACK,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK,
+ "Authentication callback",
+ "Password-finding callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA:
+ *
+ * Alias for the #SoupAuthDomainDigest:auth-callback property.
+ * (The #SoupAuthDomainDigestAuthCallback.)
+ **/
+ /**
+ * SoupAuthDomainDigest:auth-data:
+ *
+ * The data to pass to the #SoupAuthDomainDigestAuthCallback
+ */
+ g_object_class_install_property (
+ object_class, PROP_AUTH_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA,
+ "Authentication callback data",
+ "Data to pass to authentication callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
diff --git a/libsoup/server/soup-auth-domain-digest.h b/libsoup/server/soup-auth-domain-digest.h
new file mode 100644
index 00000000..523497ce
--- /dev/null
+++ b/libsoup/server/soup-auth-domain-digest.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#pragma once
+
+#include "soup-auth-domain.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH_DOMAIN_DIGEST (soup_auth_domain_digest_get_type ())
+SOUP_AVAILABLE_IN_2_4
+G_DECLARE_FINAL_TYPE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP, AUTH_DOMAIN_DIGEST, SoupAuthDomain)
+
+#define SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK "auth-callback"
+#define SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA "auth-data"
+
+SOUP_AVAILABLE_IN_2_4
+SoupAuthDomain *soup_auth_domain_digest_new (const char *optname1,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef char * (*SoupAuthDomainDigestAuthCallback) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ gpointer user_data);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainDigestAuthCallback callback,
+ gpointer user_data,
+ GDestroyNotify dnotify);
+
+SOUP_AVAILABLE_IN_2_4
+char *soup_auth_domain_digest_encode_password (const char *username,
+ const char *realm,
+ const char *password);
+
+G_END_DECLS
diff --git a/libsoup/server/soup-auth-domain.c b/libsoup/server/soup-auth-domain.c
new file mode 100644
index 00000000..8785c474
--- /dev/null
+++ b/libsoup/server/soup-auth-domain.c
@@ -0,0 +1,635 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-auth-domain.c: HTTP Authentication Domain (server-side)
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-auth-domain.h"
+#include "soup.h"
+#include "soup-path-map.h"
+
+/**
+ * SECTION:soup-auth-domain
+ * @short_description: Server-side authentication
+ * @see_also: #SoupServer
+ *
+ * A #SoupAuthDomain manages authentication for all or part of a
+ * #SoupServer. To make a server require authentication, first create
+ * an appropriate subclass of #SoupAuthDomain, and then add it to the
+ * server with soup_server_add_auth_domain().
+ *
+ * In order for an auth domain to have any effect, you must add one or
+ * more paths to it (via soup_auth_domain_add_path() or the
+ * %SOUP_AUTH_DOMAIN_ADD_PATH property). To require authentication for
+ * all ordinary requests, add the path "/". (Note that this does not
+ * include the special "*" URI (eg, "OPTIONS *"), which must be added
+ * as a separate path if you want to cover it.)
+ *
+ * If you need greater control over which requests should and
+ * shouldn't be authenticated, add paths covering everything you
+ * <emphasis>might</emphasis> want authenticated, and then use a
+ * filter (soup_auth_domain_set_filter()) to bypass authentication for
+ * those requests that don't need it.
+ **/
+
+enum {
+ PROP_0,
+
+ PROP_REALM,
+ PROP_PROXY,
+ PROP_ADD_PATH,
+ PROP_REMOVE_PATH,
+ PROP_FILTER,
+ PROP_FILTER_DATA,
+ PROP_GENERIC_AUTH_CALLBACK,
+ PROP_GENERIC_AUTH_DATA,
+
+ LAST_PROP
+};
+
+typedef struct {
+ char *realm;
+ gboolean proxy;
+ SoupPathMap *paths;
+
+ SoupAuthDomainFilter filter;
+ gpointer filter_data;
+ GDestroyNotify filter_dnotify;
+
+ SoupAuthDomainGenericAuthCallback auth_callback;
+ gpointer auth_data;
+ GDestroyNotify auth_dnotify;
+
+} SoupAuthDomainPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (SoupAuthDomain, soup_auth_domain, G_TYPE_OBJECT)
+
+static void
+soup_auth_domain_init (SoupAuthDomain *domain)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ priv->paths = soup_path_map_new (NULL);
+}
+
+static void
+soup_auth_domain_finalize (GObject *object)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (SOUP_AUTH_DOMAIN (object));
+
+ g_free (priv->realm);
+ soup_path_map_free (priv->paths);
+
+ if (priv->filter_dnotify)
+ priv->filter_dnotify (priv->filter_data);
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ G_OBJECT_CLASS (soup_auth_domain_parent_class)->finalize (object);
+}
+
+static void
+soup_auth_domain_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomain *auth_domain = SOUP_AUTH_DOMAIN (object);
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (auth_domain);
+
+ switch (prop_id) {
+ case PROP_REALM:
+ g_free (priv->realm);
+ priv->realm = g_value_dup_string (value);
+ break;
+ case PROP_PROXY:
+ priv->proxy = g_value_get_boolean (value);
+ break;
+ case PROP_ADD_PATH:
+ soup_auth_domain_add_path (auth_domain,
+ g_value_get_string (value));
+ break;
+ case PROP_REMOVE_PATH:
+ soup_auth_domain_remove_path (auth_domain,
+ g_value_get_string (value));
+ break;
+ case PROP_FILTER:
+ priv->filter = g_value_get_pointer (value);
+ break;
+ case PROP_FILTER_DATA:
+ if (priv->filter_dnotify) {
+ priv->filter_dnotify (priv->filter_data);
+ priv->filter_dnotify = NULL;
+ }
+ priv->filter_data = g_value_get_pointer (value);
+ break;
+ case PROP_GENERIC_AUTH_CALLBACK:
+ priv->auth_callback = g_value_get_pointer (value);
+ break;
+ case PROP_GENERIC_AUTH_DATA:
+ if (priv->auth_dnotify) {
+ priv->auth_dnotify (priv->auth_data);
+ priv->auth_dnotify = NULL;
+ }
+ priv->auth_data = g_value_get_pointer (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (SOUP_AUTH_DOMAIN (object));
+
+ switch (prop_id) {
+ case PROP_REALM:
+ g_value_set_string (value, priv->realm);
+ break;
+ case PROP_PROXY:
+ g_value_set_boolean (value, priv->proxy);
+ break;
+ case PROP_FILTER:
+ g_value_set_pointer (value, priv->filter);
+ break;
+ case PROP_FILTER_DATA:
+ g_value_set_pointer (value, priv->filter_data);
+ break;
+ case PROP_GENERIC_AUTH_CALLBACK:
+ g_value_set_pointer (value, priv->auth_callback);
+ break;
+ case PROP_GENERIC_AUTH_DATA:
+ g_value_set_pointer (value, priv->auth_data);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_auth_domain_class_init (SoupAuthDomainClass *auth_domain_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (auth_domain_class);
+
+ object_class->finalize = soup_auth_domain_finalize;
+ object_class->set_property = soup_auth_domain_set_property;
+ object_class->get_property = soup_auth_domain_get_property;
+
+ /**
+ * SOUP_AUTH_DOMAIN_REALM:
+ *
+ * Alias for the #SoupAuthDomain:realm property. (The realm of
+ * this auth domain.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_REALM,
+ g_param_spec_string (SOUP_AUTH_DOMAIN_REALM,
+ "Realm",
+ "The realm of this auth domain",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_PROXY:
+ *
+ * Alias for the #SoupAuthDomain:proxy property. (Whether or
+ * not this is a proxy auth domain.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_PROXY,
+ g_param_spec_boolean (SOUP_AUTH_DOMAIN_PROXY,
+ "Proxy",
+ "Whether or not this is a proxy auth domain",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_ADD_PATH:
+ *
+ * Alias for the #SoupAuthDomain:add-path property. (Shortcut
+ * for calling soup_auth_domain_add_path().)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_ADD_PATH,
+ g_param_spec_string (SOUP_AUTH_DOMAIN_ADD_PATH,
+ "Add a path",
+ "Add a path covered by this auth domain",
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_REMOVE_PATH:
+ *
+ * Alias for the #SoupAuthDomain:remove-path property.
+ * (Shortcut for calling soup_auth_domain_remove_path().)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_REMOVE_PATH,
+ g_param_spec_string (SOUP_AUTH_DOMAIN_REMOVE_PATH,
+ "Remove a path",
+ "Remove a path covered by this auth domain",
+ NULL,
+ G_PARAM_WRITABLE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_FILTER:
+ *
+ * Alias for the #SoupAuthDomain:filter property. (The
+ * #SoupAuthDomainFilter for the domain.)
+ **/
+ /**
+ * SoupAuthDomain:filter: (type SoupAuthDomainFilter)
+ *
+ * The #SoupAuthDomainFilter for the domain
+ */
+ g_object_class_install_property (
+ object_class, PROP_FILTER,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER,
+ "Filter",
+ "A filter for deciding whether or not to require authentication",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_FILTER_DATA:
+ *
+ * Alias for the #SoupAuthDomain:filter-data property. (Data
+ * to pass to the #SoupAuthDomainFilter.)
+ **/
+ /**
+ * SoupAuthDomain:generic-auth-callback: (type SoupAuthDomainGenericAuthCallback)
+ *
+ * The #SoupAuthDomainGenericAuthCallback for the domain
+ */
+ g_object_class_install_property (
+ object_class, PROP_FILTER_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER_DATA,
+ "Filter data",
+ "Data to pass to filter",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK:
+ *
+ * Alias for the #SoupAuthDomain:generic-auth-callback property.
+ * (The #SoupAuthDomainGenericAuthCallback.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_GENERIC_AUTH_CALLBACK,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK,
+ "Generic authentication callback",
+ "An authentication callback that can be used with any SoupAuthDomain subclass",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ /**
+ * SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA:
+ *
+ * Alias for the #SoupAuthDomain:generic-auth-data property.
+ * (The data to pass to the #SoupAuthDomainGenericAuthCallback.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_GENERIC_AUTH_DATA,
+ g_param_spec_pointer (SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA,
+ "Authentication callback data",
+ "Data to pass to auth callback",
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * soup_auth_domain_add_path:
+ * @domain: a #SoupAuthDomain
+ * @path: the path to add to @domain
+ *
+ * Adds @path to @domain, such that requests under @path on @domain's
+ * server will require authentication (unless overridden by
+ * soup_auth_domain_remove_path() or soup_auth_domain_set_filter()).
+ *
+ * You can also add paths by setting the %SOUP_AUTH_DOMAIN_ADD_PATH
+ * property, which can also be used to add one or more paths at
+ * construct time.
+ **/
+void
+soup_auth_domain_add_path (SoupAuthDomain *domain, const char *path)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ /* "" should not match "*" */
+ if (!*path)
+ path = "/";
+
+ soup_path_map_add (priv->paths, path, GINT_TO_POINTER (TRUE));
+}
+
+/**
+ * soup_auth_domain_remove_path:
+ * @domain: a #SoupAuthDomain
+ * @path: the path to remove from @domain
+ *
+ * Removes @path from @domain, such that requests under @path on
+ * @domain's server will NOT require authentication.
+ *
+ * This is not simply an undo-er for soup_auth_domain_add_path(); it
+ * can be used to "carve out" a subtree that does not require
+ * authentication inside a hierarchy that does. Note also that unlike
+ * with soup_auth_domain_add_path(), this cannot be overridden by
+ * adding a filter, as filters can only bypass authentication that
+ * would otherwise be required, not require it where it would
+ * otherwise be unnecessary.
+ *
+ * You can also remove paths by setting the
+ * %SOUP_AUTH_DOMAIN_REMOVE_PATH property, which can also be used to
+ * remove one or more paths at construct time.
+ **/
+void
+soup_auth_domain_remove_path (SoupAuthDomain *domain, const char *path)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ /* "" should not match "*" */
+ if (!*path)
+ path = "/";
+
+ soup_path_map_add (priv->paths, path, GINT_TO_POINTER (FALSE));
+}
+
+/**
+ * SoupAuthDomainFilter:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ * @user_data: the data passed to soup_auth_domain_set_filter()
+ *
+ * The prototype for a #SoupAuthDomain filter; see
+ * soup_auth_domain_set_filter() for details.
+ *
+ * Return value: %TRUE if @msg requires authentication, %FALSE if not.
+ **/
+
+/**
+ * soup_auth_domain_set_filter:
+ * @domain: a #SoupAuthDomain
+ * @filter: the auth filter for @domain
+ * @filter_data: data to pass to @filter
+ * @dnotify: destroy notifier to free @filter_data when @domain
+ * is destroyed
+ *
+ * Adds @filter as an authentication filter to @domain. The filter
+ * gets a chance to bypass authentication for certain requests that
+ * would otherwise require it. Eg, it might check the message's path
+ * in some way that is too complicated to do via the other methods, or
+ * it might check the message's method, and allow GETs but not PUTs.
+ *
+ * The filter function returns %TRUE if the request should still
+ * require authentication, or %FALSE if authentication is unnecessary
+ * for this request.
+ *
+ * To help prevent security holes, your filter should return %TRUE by
+ * default, and only return %FALSE under specifically-tested
+ * circumstances, rather than the other way around. Eg, in the example
+ * above, where you want to authenticate PUTs but not GETs, you should
+ * check if the method is GET and return %FALSE in that case, and then
+ * return %TRUE for all other methods (rather than returning %TRUE for
+ * PUT and %FALSE for all other methods). This way if it turned out
+ * (now or later) that some paths supported additional methods besides
+ * GET and PUT, those methods would default to being NOT allowed for
+ * unauthenticated users.
+ *
+ * You can also set the filter by setting the %SOUP_AUTH_DOMAIN_FILTER
+ * and %SOUP_AUTH_DOMAIN_FILTER_DATA properties, which can also be
+ * used to set the filter at construct time.
+ **/
+void
+soup_auth_domain_set_filter (SoupAuthDomain *domain,
+ SoupAuthDomainFilter filter,
+ gpointer filter_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ if (priv->filter_dnotify)
+ priv->filter_dnotify (priv->filter_data);
+
+ priv->filter = filter;
+ priv->filter_data = filter_data;
+ priv->filter_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER_DATA);
+}
+
+/**
+ * soup_auth_domain_get_realm:
+ * @domain: a #SoupAuthDomain
+ *
+ * Gets the realm name associated with @domain
+ *
+ * Return value: @domain's realm
+ **/
+const char *
+soup_auth_domain_get_realm (SoupAuthDomain *domain)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ return priv->realm;
+}
+
+/**
+ * SoupAuthDomainGenericAuthCallback:
+ * @domain: a #SoupAuthDomain
+ * @msg: the #SoupMessage being authenticated
+ * @username: the username from @msg
+ * @user_data: the data passed to
+ * soup_auth_domain_set_generic_auth_callback()
+ *
+ * The prototype for a #SoupAuthDomain generic authentication callback.
+ *
+ * The callback should look up the user's password, call
+ * soup_auth_domain_check_password(), and use the return value from
+ * that method as its own return value.
+ *
+ * In general, for security reasons, it is preferable to use the
+ * auth-domain-specific auth callbacks (eg,
+ * #SoupAuthDomainBasicAuthCallback and
+ * #SoupAuthDomainDigestAuthCallback), because they don't require
+ * keeping a cleartext password database. Most users will use the same
+ * password for many different sites, meaning if any site with a
+ * cleartext password database is compromised, accounts on other
+ * servers might be compromised as well. For many of the cases where
+ * #SoupServer is used, this is not really relevant, but it may still
+ * be worth considering.
+ *
+ * Return value: %TRUE if @msg is authenticated, %FALSE if not.
+ **/
+
+/**
+ * soup_auth_domain_set_generic_auth_callback:
+ * @domain: a #SoupAuthDomain
+ * @auth_callback: the auth callback
+ * @auth_data: data to pass to @auth_callback
+ * @dnotify: destroy notifier to free @auth_data when @domain
+ * is destroyed
+ *
+ * Sets @auth_callback as an authentication-handling callback for
+ * @domain. Whenever a request comes in to @domain which cannot be
+ * authenticated via a domain-specific auth callback (eg,
+ * #SoupAuthDomainDigestAuthCallback), the generic auth callback
+ * will be invoked. See #SoupAuthDomainGenericAuthCallback for information
+ * on what the callback should do.
+ **/
+void
+soup_auth_domain_set_generic_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainGenericAuthCallback auth_callback,
+ gpointer auth_data,
+ GDestroyNotify dnotify)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ if (priv->auth_dnotify)
+ priv->auth_dnotify (priv->auth_data);
+
+ priv->auth_callback = auth_callback;
+ priv->auth_data = auth_data;
+ priv->auth_dnotify = dnotify;
+
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK);
+ g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA);
+}
+
+gboolean
+soup_auth_domain_try_generic_auth_callback (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+
+ if (priv->auth_callback)
+ return priv->auth_callback (domain, msg, username, priv->auth_data);
+ else
+ return FALSE;
+}
+
+/**
+ * soup_auth_domain_check_password:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ * @username: a username
+ * @password: a password
+ *
+ * Checks if @msg authenticates to @domain via @username and
+ * @password. This would normally be called from a
+ * #SoupAuthDomainGenericAuthCallback.
+ *
+ * Return value: whether or not the message is authenticated
+ **/
+gboolean
+soup_auth_domain_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password)
+{
+ return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->check_password (domain, msg,
+ username,
+ password);
+}
+
+/**
+ * soup_auth_domain_covers:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ *
+ * Checks if @domain requires @msg to be authenticated (according to
+ * its paths and filter function). This does not actually look at
+ * whether @msg <emphasis>is</emphasis> authenticated, merely whether
+ * or not it needs to be.
+ *
+ * This is used by #SoupServer internally and is probably of no use to
+ * anyone else.
+ *
+ * Return value: %TRUE if @domain requires @msg to be authenticated
+ **/
+gboolean
+soup_auth_domain_covers (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+ const char *path;
+
+ if (!priv->proxy) {
+ path = soup_message_get_uri (msg)->path;
+ if (!soup_path_map_lookup (priv->paths, path))
+ return FALSE;
+ }
+
+ if (priv->filter && !priv->filter (domain, msg, priv->filter_data))
+ return FALSE;
+ else
+ return TRUE;
+}
+
+/**
+ * soup_auth_domain_accepts:
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ *
+ * Checks if @msg contains appropriate authorization for @domain to
+ * accept it. Mirroring soup_auth_domain_covers(), this does not check
+ * whether or not @domain <emphasis>cares</emphasis> if @msg is
+ * authorized.
+ *
+ * This is used by #SoupServer internally and is probably of no use to
+ * anyone else.
+ *
+ * Return value: (nullable): the username that @msg has authenticated
+ * as, if in fact it has authenticated. %NULL otherwise.
+ **/
+char *
+soup_auth_domain_accepts (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+ const char *header;
+
+ header = soup_message_headers_get_one (msg->request_headers,
+ priv->proxy ?
+ "Proxy-Authorization" :
+ "Authorization");
+ if (!header)
+ return NULL;
+ return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->accepts (domain, msg, header);
+}
+
+/**
+ * soup_auth_domain_challenge: (virtual challenge)
+ * @domain: a #SoupAuthDomain
+ * @msg: a #SoupMessage
+ *
+ * Adds a "WWW-Authenticate" or "Proxy-Authenticate" header to @msg,
+ * requesting that the client authenticate, and sets @msg's status
+ * accordingly.
+ *
+ * This is used by #SoupServer internally and is probably of no use to
+ * anyone else.
+ **/
+void
+soup_auth_domain_challenge (SoupAuthDomain *domain, SoupMessage *msg)
+{
+ SoupAuthDomainPrivate *priv = soup_auth_domain_get_instance_private (domain);
+ char *challenge;
+
+ challenge = SOUP_AUTH_DOMAIN_GET_CLASS (domain)->challenge (domain, msg);
+ soup_message_set_status (msg, priv->proxy ?
+ SOUP_STATUS_PROXY_UNAUTHORIZED :
+ SOUP_STATUS_UNAUTHORIZED);
+ soup_message_headers_append (msg->response_headers,
+ priv->proxy ?
+ "Proxy-Authenticate" :
+ "WWW-Authenticate",
+ challenge);
+ g_free (challenge);
+}
diff --git a/libsoup/server/soup-auth-domain.h b/libsoup/server/soup-auth-domain.h
new file mode 100644
index 00000000..397dc53b
--- /dev/null
+++ b/libsoup/server/soup-auth-domain.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#pragma once
+
+#include "soup-types.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_AUTH_DOMAIN (soup_auth_domain_get_type ())
+SOUP_AVAILABLE_IN_2_4
+G_DECLARE_DERIVABLE_TYPE (SoupAuthDomain, soup_auth_domain, SOUP, AUTH_DOMAIN, GObject)
+
+struct _SoupAuthDomainClass {
+ GObjectClass parent_class;
+
+ char * (*accepts) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *header);
+ char * (*challenge) (SoupAuthDomain *domain,
+ SoupMessage *msg);
+ gboolean (*check_password) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password);
+ gpointer padding[6];
+};
+
+#define SOUP_AUTH_DOMAIN_REALM "realm"
+#define SOUP_AUTH_DOMAIN_PROXY "proxy"
+#define SOUP_AUTH_DOMAIN_ADD_PATH "add-path"
+#define SOUP_AUTH_DOMAIN_REMOVE_PATH "remove-path"
+#define SOUP_AUTH_DOMAIN_FILTER "filter"
+#define SOUP_AUTH_DOMAIN_FILTER_DATA "filter-data"
+#define SOUP_AUTH_DOMAIN_GENERIC_AUTH_CALLBACK "generic-auth-callback"
+#define SOUP_AUTH_DOMAIN_GENERIC_AUTH_DATA "generic-auth-data"
+
+typedef gboolean (*SoupAuthDomainFilter) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ gpointer user_data);
+
+typedef gboolean (*SoupAuthDomainGenericAuthCallback) (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ gpointer user_data);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_add_path (SoupAuthDomain *domain,
+ const char *path);
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_remove_path (SoupAuthDomain *domain,
+ const char *path);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_set_filter (SoupAuthDomain *domain,
+ SoupAuthDomainFilter filter,
+ gpointer filter_data,
+ GDestroyNotify dnotify);
+
+SOUP_AVAILABLE_IN_2_4
+const char *soup_auth_domain_get_realm (SoupAuthDomain *domain);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_set_generic_auth_callback (SoupAuthDomain *domain,
+ SoupAuthDomainGenericAuthCallback auth_callback,
+ gpointer auth_data,
+ GDestroyNotify dnotify);
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_domain_check_password (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username,
+ const char *password);
+
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_domain_covers (SoupAuthDomain *domain,
+ SoupMessage *msg);
+SOUP_AVAILABLE_IN_2_4
+char *soup_auth_domain_accepts (SoupAuthDomain *domain,
+ SoupMessage *msg);
+SOUP_AVAILABLE_IN_2_4
+void soup_auth_domain_challenge (SoupAuthDomain *domain,
+ SoupMessage *msg);
+
+/* protected */
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_auth_domain_try_generic_auth_callback (SoupAuthDomain *domain,
+ SoupMessage *msg,
+ const char *username);
+
+G_END_DECLS
diff --git a/libsoup/server/soup-message-server-io.c b/libsoup/server/soup-message-server-io.c
new file mode 100644
index 00000000..acd1490c
--- /dev/null
+++ b/libsoup/server/soup-message-server-io.c
@@ -0,0 +1,304 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-message-server-io.c: server-side request/response
+ *
+ * Copyright (C) 2000-2003, Ximian, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "soup.h"
+#include "soup-message-private.h"
+#include "soup-misc.h"
+#include "soup-socket-private.h"
+
+static SoupURI *
+parse_connect_authority (const char *req_path)
+{
+ SoupURI *uri;
+ char *fake_uri;
+
+ fake_uri = g_strdup_printf ("http://%s", req_path);
+ uri = soup_uri_new (fake_uri);
+ g_free (fake_uri);
+
+ if (uri->user || uri->password ||
+ uri->query || uri->fragment ||
+ !uri->host ||
+ (uri->port == 0) ||
+ (strcmp (uri->path, "/") != 0)) {
+ soup_uri_free (uri);
+ return NULL;
+ }
+
+ return uri;
+}
+
+static guint
+parse_request_headers (SoupMessage *msg, char *headers, guint headers_len,
+ SoupEncoding *encoding, gpointer sock, GError **error)
+{
+ char *req_method, *req_path, *url;
+ SoupHTTPVersion version;
+ const char *req_host;
+ guint status;
+ SoupURI *uri;
+
+ status = soup_headers_parse_request (headers, headers_len,
+ msg->request_headers,
+ &req_method,
+ &req_path,
+ &version);
+ if (!SOUP_STATUS_IS_SUCCESSFUL (status)) {
+ if (status == SOUP_STATUS_MALFORMED) {
+ g_set_error_literal (error, SOUP_REQUEST_ERROR,
+ SOUP_REQUEST_ERROR_PARSING,
+ _("Could not parse HTTP request"));
+ }
+ return status;
+ }
+
+ g_object_set (G_OBJECT (msg),
+ SOUP_MESSAGE_METHOD, req_method,
+ SOUP_MESSAGE_HTTP_VERSION, version,
+ NULL);
+ g_free (req_method);
+
+ /* Handle request body encoding */
+ *encoding = soup_message_headers_get_encoding (msg->request_headers);
+ if (*encoding == SOUP_ENCODING_UNRECOGNIZED) {
+ if (soup_message_headers_get_list (msg->request_headers, "Transfer-Encoding"))
+ return SOUP_STATUS_NOT_IMPLEMENTED;
+ else
+ return SOUP_STATUS_BAD_REQUEST;
+ }
+
+ /* Generate correct context for request */
+ req_host = soup_message_headers_get_one (msg->request_headers, "Host");
+ if (req_host && strchr (req_host, '/')) {
+ g_free (req_path);
+ return SOUP_STATUS_BAD_REQUEST;
+ }
+
+ if (!strcmp (req_path, "*") && req_host) {
+ /* Eg, "OPTIONS * HTTP/1.1" */
+ url = g_strdup_printf ("%s://%s",
+ soup_socket_is_ssl (sock) ? "https" : "http",
+ req_host);
+ uri = soup_uri_new (url);
+ if (uri)
+ soup_uri_set_path (uri, "*");
+ g_free (url);
+ } else if (msg->method == SOUP_METHOD_CONNECT) {
+ /* Authority */
+ uri = parse_connect_authority (req_path);
+ } else if (*req_path != '/') {
+ /* Absolute URI */
+ uri = soup_uri_new (req_path);
+ } else if (req_host) {
+ url = g_strdup_printf ("%s://%s%s",
+ soup_socket_is_ssl (sock) ? "https" : "http",
+ req_host, req_path);
+ uri = soup_uri_new (url);
+ g_free (url);
+ } else if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
+ /* No Host header, no AbsoluteUri */
+ GInetSocketAddress *addr = soup_socket_get_local_address (sock);
+ GInetAddress *inet_addr = g_inet_socket_address_get_address (addr);
+ char *local_ip = g_inet_address_to_string (inet_addr);
+
+ uri = soup_uri_new (NULL);
+ soup_uri_set_scheme (uri, soup_socket_is_ssl (sock) ?
+ SOUP_URI_SCHEME_HTTPS :
+ SOUP_URI_SCHEME_HTTP);
+ soup_uri_set_host (uri, local_ip);
+ soup_uri_set_port (uri, g_inet_socket_address_get_port (addr));
+ soup_uri_set_path (uri, req_path);
+ g_free (local_ip);
+ } else
+ uri = NULL;
+
+ g_free (req_path);
+
+ if (!uri || !uri->host) {
+ if (uri)
+ soup_uri_free (uri);
+ return SOUP_STATUS_BAD_REQUEST;
+ }
+
+ soup_message_set_uri (msg, uri);
+ soup_uri_free (uri);
+
+ return SOUP_STATUS_OK;
+}
+
+static void
+handle_partial_get (SoupMessage *msg)
+{
+ SoupRange *ranges;
+ int nranges;
+ GBytes *full_response;
+ guint status;
+
+ /* Make sure the message is set up right for us to return a
+ * partial response; it has to be a GET, the status must be
+ * 200 OK (and in particular, NOT already 206 Partial
+ * Content), and the SoupServer must have already filled in
+ * the response body
+ */
+ if (msg->method != SOUP_METHOD_GET ||
+ msg->status_code != SOUP_STATUS_OK ||
+ soup_message_headers_get_encoding (msg->response_headers) !=
+ SOUP_ENCODING_CONTENT_LENGTH ||
+ msg->response_body->length == 0 ||
+ !soup_message_body_get_accumulate (msg->response_body))
+ return;
+
+ /* Oh, and there has to have been a valid Range header on the
+ * request, of course.
+ */
+ status = soup_message_headers_get_ranges_internal (msg->request_headers,
+ msg->response_body->length,
+ TRUE,
+ &ranges, &nranges);
+ if (status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) {
+ soup_message_set_status (msg, status);
+ soup_message_body_truncate (msg->response_body);
+ return;
+ } else if (status != SOUP_STATUS_PARTIAL_CONTENT)
+ return;
+
+ full_response = soup_message_body_flatten (msg->response_body);
+ if (!full_response) {
+ soup_message_headers_free_ranges (msg->request_headers, ranges);
+ return;
+ }
+
+ soup_message_set_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
+ soup_message_body_truncate (msg->response_body);
+
+ if (nranges == 1) {
+ GBytes *range_buf;
+
+ /* Single range, so just set Content-Range and fix the body. */
+
+ soup_message_headers_set_content_range (msg->response_headers,
+ ranges[0].start,
+ ranges[0].end,
+ g_bytes_get_size (full_response));
+ range_buf = g_bytes_new_from_bytes (full_response,
+ ranges[0].start,
+ ranges[0].end - ranges[0].start + 1);
+ soup_message_body_append_bytes (msg->response_body, range_buf);
+ g_bytes_unref (range_buf);
+ } else {
+ SoupMultipart *multipart;
+ SoupMessageHeaders *part_headers;
+ GBytes *part_body;
+ GBytes *body = NULL;
+ const char *content_type;
+ int i;
+
+ /* Multiple ranges, so build a multipart/byteranges response
+ * to replace msg->response_body with.
+ */
+
+ multipart = soup_multipart_new ("multipart/byteranges");
+ content_type = soup_message_headers_get_one (msg->response_headers,
+ "Content-Type");
+ for (i = 0; i < nranges; i++) {
+ part_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
+ if (content_type) {
+ soup_message_headers_append (part_headers,
+ "Content-Type",
+ content_type);
+ }
+ soup_message_headers_set_content_range (part_headers,
+ ranges[i].start,
+ ranges[i].end,
+ g_bytes_get_size (full_response));
+ part_body = g_bytes_new_from_bytes (full_response,
+ ranges[i].start,
+ ranges[i].end - ranges[i].start + 1);
+ soup_multipart_append_part (multipart, part_headers,
+ part_body);
+ soup_message_headers_free (part_headers);
+ g_bytes_unref (part_body);
+ }
+
+ soup_multipart_to_message (multipart, msg->response_headers, &body);
+ soup_message_body_append_bytes (msg->response_body, body);
+ g_bytes_unref (body);
+ soup_multipart_free (multipart);
+ }
+
+ g_bytes_unref (full_response);
+ soup_message_headers_free_ranges (msg->request_headers, ranges);
+}
+
+static void
+get_response_headers (SoupMessage *msg, GString *headers,
+ SoupEncoding *encoding, gpointer user_data)
+{
+ SoupEncoding claimed_encoding;
+ SoupMessageHeadersIter iter;
+ const char *name, *value;
+
+ if (msg->status_code == 0)
+ soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+
+ handle_partial_get (msg);
+
+ g_string_append_printf (headers, "HTTP/1.%c %d %s\r\n",
+ soup_message_get_http_version (msg) == SOUP_HTTP_1_0 ? '0' : '1',
+ msg->status_code, msg->reason_phrase);
+
+ claimed_encoding = soup_message_headers_get_encoding (msg->response_headers);
+ if ((msg->method == SOUP_METHOD_HEAD ||
+ msg->status_code == SOUP_STATUS_NO_CONTENT ||
+ msg->status_code == SOUP_STATUS_NOT_MODIFIED ||
+ SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) ||
+ (msg->method == SOUP_METHOD_CONNECT &&
+ SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)))
+ *encoding = SOUP_ENCODING_NONE;
+ else
+ *encoding = claimed_encoding;
+
+ if (claimed_encoding == SOUP_ENCODING_CONTENT_LENGTH &&
+ !soup_message_headers_get_content_length (msg->response_headers)) {
+ soup_message_headers_set_content_length (msg->response_headers,
+ msg->response_body->length);
+ }
+
+ soup_message_headers_iter_init (&iter, msg->response_headers);
+ while (soup_message_headers_iter_next (&iter, &name, &value))
+ g_string_append_printf (headers, "%s: %s\r\n", name, value);
+ g_string_append (headers, "\r\n");
+}
+
+void
+soup_message_read_request (SoupMessage *msg,
+ SoupSocket *sock,
+ SoupMessageCompletionFn completion_cb,
+ gpointer user_data)
+{
+ GMainContext *async_context;
+ GIOStream *iostream;
+
+ async_context = g_main_context_ref_thread_default ();
+ iostream = soup_socket_get_iostream (sock);
+
+ soup_message_io_server (msg, iostream, async_context,
+ get_response_headers,
+ parse_request_headers,
+ sock,
+ completion_cb, user_data);
+ g_main_context_unref (async_context);
+}
diff --git a/libsoup/server/soup-path-map.c b/libsoup/server/soup-path-map.c
new file mode 100644
index 00000000..6be8eb67
--- /dev/null
+++ b/libsoup/server/soup-path-map.c
@@ -0,0 +1,192 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-path-map.c: URI path prefix-matcher
+ *
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "soup-path-map.h"
+
+/* This could be replaced with something more clever, like a Patricia
+ * trie, but it's probably not worth it since the total number of
+ * mappings is likely to always be small. So we keep an array of
+ * paths, sorted by decreasing length. (The first prefix match will
+ * therefore be the longest.)
+ */
+
+typedef struct {
+ char *path;
+ int len;
+ gpointer data;
+} SoupPathMapping;
+
+struct SoupPathMap {
+ GArray *mappings;
+ GDestroyNotify free_func;
+};
+
+/**
+ * soup_path_map_new:
+ * @data_free_func: function to use to free data added with
+ * soup_path_map_add().
+ *
+ * Creates a new %SoupPathMap.
+ *
+ * Return value: the new %SoupPathMap
+ **/
+SoupPathMap *
+soup_path_map_new (GDestroyNotify data_free_func)
+{
+ SoupPathMap *map;
+
+ map = g_slice_new0 (SoupPathMap);
+ map->mappings = g_array_new (FALSE, FALSE, sizeof (SoupPathMapping));
+ map->free_func = data_free_func;
+
+ return map;
+}
+
+/**
+ * soup_path_map_free:
+ * @map: a %SoupPathMap
+ *
+ * Frees @map and all data stored in it.
+ **/
+void
+soup_path_map_free (SoupPathMap *map)
+{
+ SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
+ guint i;
+
+ for (i = 0; i < map->mappings->len; i++) {
+ g_free (mappings[i].path);
+ if (map->free_func)
+ map->free_func (mappings[i].data);
+ }
+ g_array_free (map->mappings, TRUE);
+
+ g_slice_free (SoupPathMap, map);
+}
+
+/* Scan @map looking for @path or one of its ancestors.
+ * Sets *@match to the index of a match, or -1 if no match is found.
+ * Sets *@insert to the index to insert @path at if a new mapping is
+ * desired. Returns %TRUE if *@match is an exact match.
+ */
+static gboolean
+mapping_lookup (SoupPathMap *map, const char *path, int *match, int *insert)
+{
+ SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
+ guint i;
+ int path_len;
+ gboolean exact = FALSE;
+
+ *match = -1;
+
+ path_len = strcspn (path, "?");
+ for (i = 0; i < map->mappings->len; i++) {
+ if (mappings[i].len > path_len)
+ continue;
+
+ if (insert && mappings[i].len < path_len) {
+ *insert = i;
+ /* Clear insert so we don't try to set it again */
+ insert = NULL;
+ }
+
+ if (!strncmp (mappings[i].path, path, mappings[i].len)) {
+ *match = i;
+ if (path_len == mappings[i].len)
+ exact = TRUE;
+ if (!insert)
+ return exact;
+ }
+ }
+
+ if (insert)
+ *insert = i;
+ return exact;
+}
+
+/**
+ * soup_path_map_add:
+ * @map: a %SoupPathMap
+ * @path: the path
+ * @data: the data
+ *
+ * Adds @data to @map at @path. If there was already data at @path it
+ * will be freed.
+ **/
+void
+soup_path_map_add (SoupPathMap *map, const char *path, gpointer data)
+{
+ SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
+ int match, insert;
+
+ if (mapping_lookup (map, path, &match, &insert)) {
+ if (map->free_func)
+ map->free_func (mappings[match].data);
+ mappings[match].data = data;
+ } else {
+ SoupPathMapping mapping;
+
+ mapping.path = g_strdup (path);
+ mapping.len = strlen (path);
+ mapping.data = data;
+ g_array_insert_val (map->mappings, insert, mapping);
+ }
+}
+
+/**
+ * soup_path_map_remove:
+ * @map: a %SoupPathMap
+ * @path: the path
+ *
+ * Removes @data from @map at @path. (This must be called with the same
+ * path the data was originally added with, not a subdirectory.)
+ **/
+void
+soup_path_map_remove (SoupPathMap *map, const char *path)
+{
+ SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
+ int match;
+
+ if (!mapping_lookup (map, path, &match, NULL))
+ return;
+
+ if (map->free_func)
+ map->free_func (mappings[match].data);
+ g_free (mappings[match].path);
+ g_array_remove_index (map->mappings, match);
+}
+
+/**
+ * soup_path_map_lookup:
+ * @map: a %SoupPathMap
+ * @path: the path
+ *
+ * Finds the data associated with @path in @map. If there is no data
+ * specifically associated with @path, it will return the data for the
+ * closest parent directory of @path that has data associated with it.
+ *
+ * Return value: (nullable): the data set with soup_path_map_add(), or
+ * %NULL if no data could be found for @path or any of its ancestors.
+ **/
+gpointer
+soup_path_map_lookup (SoupPathMap *map, const char *path)
+{
+ SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data;
+ int match;
+
+ mapping_lookup (map, path, &match, NULL);
+ if (match == -1)
+ return NULL;
+ else
+ return mappings[match].data;
+}
diff --git a/libsoup/server/soup-path-map.h b/libsoup/server/soup-path-map.h
new file mode 100644
index 00000000..8c3b3367
--- /dev/null
+++ b/libsoup/server/soup-path-map.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ */
+
+#ifndef __SOUP_PATH_MAP_H__
+#define __SOUP_PATH_MAP_H__ 1
+
+#include "soup-types.h"
+
+typedef struct SoupPathMap SoupPathMap;
+
+SoupPathMap *soup_path_map_new (GDestroyNotify data_free_func);
+void soup_path_map_free (SoupPathMap *map);
+
+void soup_path_map_add (SoupPathMap *map,
+ const char *path,
+ gpointer data);
+void soup_path_map_remove (SoupPathMap *map,
+ const char *path);
+
+gpointer soup_path_map_lookup (SoupPathMap *map,
+ const char *path);
+
+
+#endif /* __SOUP_PATH_MAP_H__ */
diff --git a/libsoup/server/soup-server-private.h b/libsoup/server/soup-server-private.h
new file mode 100644
index 00000000..15430e73
--- /dev/null
+++ b/libsoup/server/soup-server-private.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 Igalia S.L.
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include "soup-server.h"
+
+SoupSocket *soup_client_context_get_soup_socket (SoupClientContext *client);
diff --git a/libsoup/server/soup-server.c b/libsoup/server/soup-server.c
new file mode 100644
index 00000000..aa5f2203
--- /dev/null
+++ b/libsoup/server/soup-server.c
@@ -0,0 +1,2364 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * soup-server.c: Asynchronous HTTP server
+ *
+ * Copyright (C) 2001-2003, Ximian, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include "soup-server-private.h"
+#include "soup.h"
+#include "soup-message-private.h"
+#include "soup-misc.h"
+#include "soup-path-map.h"
+#include "soup-socket-private.h"
+#include "websocket/soup-websocket.h"
+#include "websocket/soup-websocket-connection.h"
+#include "websocket/soup-websocket-extension-deflate.h"
+
+/**
+ * SECTION:soup-server
+ * @short_description: HTTP server
+ * @see_also: #SoupAuthDomain
+ *
+ * #SoupServer implements a simple HTTP server.
+ *
+ * (The following documentation describes the current #SoupServer API,
+ * available in <application>libsoup</application> 2.48 and later. See
+ * the section "<link linkend="soup-server-old-api">The Old SoupServer
+ * Listening API</link>" in the server how-to documentation for
+ * details on the older #SoupServer API.)
+ *
+ * To begin, create a server using soup_server_new(). Add at least one
+ * handler by calling soup_server_add_handler() or
+ * soup_server_add_early_handler(); the handler will be called to
+ * process any requests underneath the path you pass. (If you want all
+ * requests to go to the same handler, just pass "/" (or %NULL) for
+ * the path.)
+ *
+ * When a new connection is accepted (or a new request is started on
+ * an existing persistent connection), the #SoupServer will emit
+ * #SoupServer::request-started and then begin processing the request
+ * as described below, but note that once the message is assigned a
+ * #SoupMessage:status-code, then callbacks after that point will be
+ * skipped. Note also that it is not defined when the callbacks happen
+ * relative to various #SoupMessage signals.
+ *
+ * Once the headers have been read, #SoupServer will check if there is
+ * a #SoupAuthDomain (qv) covering the Request-URI; if so, and if the
+ * message does not contain suitable authorization, then the
+ * #SoupAuthDomain will set a status of %SOUP_STATUS_UNAUTHORIZED on
+ * the message.
+ *
+ * After checking for authorization, #SoupServer will look for "early"
+ * handlers (added with soup_server_add_early_handler()) matching the
+ * Request-URI. If one is found, it will be run; in particular, this
+ * can be used to connect to signals to do a streaming read of the
+ * request body.
+ *
+ * (At this point, if the request headers contain "<literal>Expect:
+ * 100-continue</literal>", and a status code has been set, then
+ * #SoupServer will skip the remaining steps and return the response.
+ * If the request headers contain "<literal>Expect:
+ * 100-continue</literal>" and no status code has been set,
+ * #SoupServer will return a %SOUP_STATUS_CONTINUE status before
+ * continuing.)
+ *
+ * The server will then read in the response body (if present). At
+ * this point, if there are no handlers at all defined for the
+ * Request-URI, then the server will return %SOUP_STATUS_NOT_FOUND to
+ * the client.
+ *
+ * Otherwise (assuming no previous step assigned a status to the
+ * message) any "normal" handlers (added with
+ * soup_server_add_handler()) for the message's Request-URI will be
+ * run.
+ *
+ * Then, if the path has a WebSocket handler registered (and has
+ * not yet been assigned a status), #SoupServer will attempt to
+ * validate the WebSocket handshake, filling in the response and
+ * setting a status of %SOUP_STATUS_SWITCHING_PROTOCOLS or
+ * %SOUP_STATUS_BAD_REQUEST accordingly.
+ *
+ * If the message still has no status code at this point (and has not
+ * been paused with soup_server_pause_message()), then it will be
+ * given a status of %SOUP_STATUS_INTERNAL_SERVER_ERROR (because at
+ * least one handler ran, but returned without assigning a status).
+ *
+ * Finally, the server will emit #SoupServer::request-finished (or
+ * #SoupServer::request-aborted if an I/O error occurred before
+ * handling was completed).
+ *
+ * If you want to handle the special "*" URI (eg, "OPTIONS *"), you
+ * must explicitly register a handler for "*"; the default handler
+ * will not be used for that case.
+ *
+ * If you want to process https connections in addition to (or instead
+ * of) http connections, you can either set the
+ * %SOUP_SERVER_TLS_CERTIFICATE property when creating the server, or
+ * else call soup_server_set_ssl_cert_file() after creating it.
+ *
+ * Once the server is set up, make one or more calls to
+ * soup_server_listen(), soup_server_listen_local(), or
+ * soup_server_listen_all() to tell it where to listen for
+ * connections. (All ports on a #SoupServer use the same handlers; if
+ * you need to handle some ports differently, such as returning
+ * different data for http and https, you'll need to create multiple
+ * #SoupServers, or else check the passed-in URI in the handler
+ * function.).
+ *
+ * #SoupServer will begin processing connections as soon as you return
+ * to (or start) the main loop for the current thread-default
+ * #GMainContext.
+ */
+
+enum {
+ REQUEST_STARTED,
+ REQUEST_READ,
+ REQUEST_FINISHED,
+ REQUEST_ABORTED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _SoupClientContext {
+ SoupServer *server;
+ SoupSocket *sock;
+ GSocket *gsock;
+ SoupMessage *msg;
+ SoupAuthDomain *auth_domain;
+ char *auth_user;
+
+ GSocketAddress *remote_addr;
+ char *remote_ip;
+ GSocketAddress *local_addr;
+
+ int ref_count;
+};
+
+typedef struct {
+ char *path;
+
+ SoupServerCallback early_callback;
+ GDestroyNotify early_destroy;
+ gpointer early_user_data;
+
+ SoupServerCallback callback;
+ GDestroyNotify destroy;
+ gpointer user_data;
+
+ char *websocket_origin;
+ char **websocket_protocols;
+ GList *websocket_extensions;
+ SoupServerWebsocketCallback websocket_callback;
+ GDestroyNotify websocket_destroy;
+ gpointer websocket_user_data;
+} SoupServerHandler;
+
+typedef struct {
+ GSList *listeners;
+ GSList *clients;
+
+ GTlsCertificate *tls_cert;
+
+ char *server_header;
+
+ GMainContext *async_context;
+ GMainLoop *loop;
+
+ gboolean raw_paths;
+ SoupPathMap *handlers;
+
+ GSList *auth_domains;
+
+ char **http_aliases, **https_aliases;
+
+ GPtrArray *websocket_extension_types;
+
+ gboolean disposed;
+
+} SoupServerPrivate;
+
+#define SOUP_SERVER_SERVER_HEADER_BASE "libsoup/" PACKAGE_VERSION
+
+enum {
+ PROP_0,
+
+ PROP_TLS_CERT_FILE,
+ PROP_TLS_KEY_FILE,
+ PROP_TLS_CERTIFICATE,
+ PROP_RAW_PATHS,
+ PROP_SERVER_HEADER,
+ PROP_HTTP_ALIASES,
+ PROP_HTTPS_ALIASES,
+ PROP_ADD_WEBSOCKET_EXTENSION,
+ PROP_REMOVE_WEBSOCKET_EXTENSION,
+
+ LAST_PROP
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (SoupServer, soup_server, G_TYPE_OBJECT)
+
+static SoupClientContext *soup_client_context_ref (SoupClientContext *client);
+static void soup_client_context_unref (SoupClientContext *client);
+
+static void
+free_handler (SoupServerHandler *handler)
+{
+ g_free (handler->path);
+ g_free (handler->websocket_origin);
+ g_strfreev (handler->websocket_protocols);
+ g_list_free_full (handler->websocket_extensions, g_object_unref);
+ if (handler->early_destroy)
+ handler->early_destroy (handler->early_user_data);
+ if (handler->destroy)
+ handler->destroy (handler->user_data);
+ if (handler->websocket_destroy)
+ handler->websocket_destroy (handler->websocket_user_data);
+ g_slice_free (SoupServerHandler, handler);
+}
+
+static void
+soup_server_init (SoupServer *server)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+
+ priv->handlers = soup_path_map_new ((GDestroyNotify)free_handler);
+
+ priv->http_aliases = g_new (char *, 2);
+ priv->http_aliases[0] = (char *)g_intern_string ("*");
+ priv->http_aliases[1] = NULL;
+
+ priv->websocket_extension_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref);
+
+ /* Use permessage-deflate extension by default */
+ g_ptr_array_add (priv->websocket_extension_types, g_type_class_ref (SOUP_TYPE_WEBSOCKET_EXTENSION_DEFLATE));
+}
+
+static void
+soup_server_dispose (GObject *object)
+{
+ SoupServer *server = SOUP_SERVER (object);
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+
+ priv->disposed = TRUE;
+ soup_server_disconnect (server);
+
+ G_OBJECT_CLASS (soup_server_parent_class)->dispose (object);
+}
+
+static void
+soup_server_finalize (GObject *object)
+{
+ SoupServer *server = SOUP_SERVER (object);
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+
+ g_clear_object (&priv->tls_cert);
+
+ g_free (priv->server_header);
+
+ soup_path_map_free (priv->handlers);
+
+ g_slist_free_full (priv->auth_domains, g_object_unref);
+
+ g_clear_pointer (&priv->loop, g_main_loop_unref);
+
+ g_free (priv->http_aliases);
+ g_free (priv->https_aliases);
+
+ g_ptr_array_free (priv->websocket_extension_types, TRUE);
+
+ G_OBJECT_CLASS (soup_server_parent_class)->finalize (object);
+}
+
+/* priv->http_aliases and priv->https_aliases are stored as arrays of
+ * *interned* strings, so we can't just use g_strdupv() to set them.
+ */
+static void
+set_aliases (char ***variable, char **value)
+{
+ int len, i;
+
+ if (*variable)
+ g_free (*variable);
+
+ if (!value) {
+ *variable = NULL;
+ return;
+ }
+
+ len = g_strv_length (value);
+ *variable = g_new (char *, len + 1);
+ for (i = 0; i < len; i++)
+ (*variable)[i] = (char *)g_intern_string (value[i]);
+ (*variable)[i] = NULL;
+}
+
+static void
+soup_server_set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ SoupServer *server = SOUP_SERVER (object);
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ const char *header;
+
+ switch (prop_id) {
+ case PROP_TLS_CERTIFICATE:
+ if (priv->tls_cert)
+ g_object_unref (priv->tls_cert);
+ priv->tls_cert = g_value_dup_object (value);
+ break;
+ case PROP_RAW_PATHS:
+ priv->raw_paths = g_value_get_boolean (value);
+ break;
+ case PROP_SERVER_HEADER:
+ g_free (priv->server_header);
+ header = g_value_get_string (value);
+ if (!header)
+ priv->server_header = NULL;
+ else if (!*header) {
+ priv->server_header =
+ g_strdup (SOUP_SERVER_SERVER_HEADER_BASE);
+ } else if (g_str_has_suffix (header, " ")) {
+ priv->server_header =
+ g_strdup_printf ("%s%s", header,
+ SOUP_SERVER_SERVER_HEADER_BASE);
+ } else
+ priv->server_header = g_strdup (header);
+ break;
+ case PROP_HTTP_ALIASES:
+ set_aliases (&priv->http_aliases, g_value_get_boxed (value));
+ break;
+ case PROP_HTTPS_ALIASES:
+ set_aliases (&priv->https_aliases, g_value_get_boxed (value));
+ break;
+ case PROP_ADD_WEBSOCKET_EXTENSION:
+ soup_server_add_websocket_extension (server, g_value_get_gtype (value));
+ break;
+ case PROP_REMOVE_WEBSOCKET_EXTENSION:
+ soup_server_remove_websocket_extension (server, g_value_get_gtype (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_server_get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ SoupServer *server = SOUP_SERVER (object);
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+
+ switch (prop_id) {
+ case PROP_TLS_CERTIFICATE:
+ g_value_set_object (value, priv->tls_cert);
+ break;
+ case PROP_RAW_PATHS:
+ g_value_set_boolean (value, priv->raw_paths);
+ break;
+ case PROP_SERVER_HEADER:
+ g_value_set_string (value, priv->server_header);
+ break;
+ case PROP_HTTP_ALIASES:
+ g_value_set_boxed (value, priv->http_aliases);
+ break;
+ case PROP_HTTPS_ALIASES:
+ g_value_set_boxed (value, priv->https_aliases);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+soup_server_class_init (SoupServerClass *server_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (server_class);
+
+ /* virtual method override */
+ object_class->dispose = soup_server_dispose;
+ object_class->finalize = soup_server_finalize;
+ object_class->set_property = soup_server_set_property;
+ object_class->get_property = soup_server_get_property;
+
+ /* signals */
+
+ /**
+ * SoupServer::request-started:
+ * @server: the server
+ * @message: the new message
+ * @client: the client context
+ *
+ * Emitted when the server has started reading a new request.
+ * @message will be completely blank; not even the
+ * Request-Line will have been read yet. About the only thing
+ * you can usefully do with it is connect to its signals.
+ *
+ * If the request is read successfully, this will eventually
+ * be followed by a #SoupServer::request_read signal. If a
+ * response is then sent, the request processing will end with
+ * a #SoupServer::request_finished signal. If a network error
+ * occurs, the processing will instead end with
+ * #SoupServer::request_aborted.
+ **/
+ signals[REQUEST_STARTED] =
+ g_signal_new ("request-started",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (SoupServerClass, request_started),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ SOUP_TYPE_MESSAGE,
+ SOUP_TYPE_CLIENT_CONTEXT);
+
+ /**
+ * SoupServer::request-read:
+ * @server: the server
+ * @message: the message
+ * @client: the client context
+ *
+ * Emitted when the server has successfully read a request.
+ * @message will have all of its request-side information
+ * filled in, and if the message was authenticated, @client
+ * will have information about that. This signal is emitted
+ * before any (non-early) handlers are called for the message,
+ * and if it sets the message's #status_code, then normal
+ * handler processing will be skipped.
+ **/
+ signals[REQUEST_READ] =
+ g_signal_new ("request-read",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (SoupServerClass, request_read),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ SOUP_TYPE_MESSAGE,
+ SOUP_TYPE_CLIENT_CONTEXT);
+
+ /**
+ * SoupServer::request-finished:
+ * @server: the server
+ * @message: the message
+ * @client: the client context
+ *
+ * Emitted when the server has finished writing a response to
+ * a request.
+ **/
+ signals[REQUEST_FINISHED] =
+ g_signal_new ("request-finished",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (SoupServerClass, request_finished),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ SOUP_TYPE_MESSAGE,
+ SOUP_TYPE_CLIENT_CONTEXT);
+
+ /**
+ * SoupServer::request-aborted:
+ * @server: the server
+ * @message: the message
+ * @client: the client context
+ *
+ * Emitted when processing has failed for a message; this
+ * could mean either that it could not be read (if
+ * #SoupServer::request_read has not been emitted for it yet),
+ * or that the response could not be written back (if
+ * #SoupServer::request_read has been emitted but
+ * #SoupServer::request_finished has not been).
+ *
+ * @message is in an undefined state when this signal is
+ * emitted; the signal exists primarily to allow the server to
+ * free any state that it may have allocated in
+ * #SoupServer::request_started.
+ **/
+ signals[REQUEST_ABORTED] =
+ g_signal_new ("request-aborted",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (SoupServerClass, request_aborted),
+ NULL, NULL,
+ NULL,
+ G_TYPE_NONE, 2,
+ SOUP_TYPE_MESSAGE,
+ SOUP_TYPE_CLIENT_CONTEXT);
+
+ /* properties */
+ /**
+ * SOUP_SERVER_TLS_CERTIFICATE:
+ *
+ * Alias for the #SoupServer:tls-certificate property, qv.
+ *
+ * Since: 2.38
+ */
+ /**
+ * SoupServer:tls-certificate:
+ *
+ * A #GTlsCertificate that has a #GTlsCertificate:private-key
+ * set. If this is set, then the server will be able to speak
+ * https in addition to (or instead of) plain http.
+ *
+ * Alternatively, you can call soup_server_set_ssl_cert_file()
+ * to have #SoupServer read in a a certificate from a file.
+ *
+ * Since: 2.38
+ */
+ g_object_class_install_property (
+ object_class, PROP_TLS_CERTIFICATE,
+ g_param_spec_object (SOUP_SERVER_TLS_CERTIFICATE,
+ "TLS certificate",
+ "GTlsCertificate to use for https",
+ G_TYPE_TLS_CERTIFICATE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SOUP_SERVER_RAW_PATHS:
+ *
+ * Alias for the #SoupServer:raw-paths property. (If %TRUE,
+ * percent-encoding in the Request-URI path will not be
+ * automatically decoded.)
+ **/
+ g_object_class_install_property (
+ object_class, PROP_RAW_PATHS,
+ g_param_spec_boolean (SOUP_SERVER_RAW_PATHS,
+ "Raw paths",
+ "If %TRUE, percent-encoding in the Request-URI path will not be automatically decoded.",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SoupServer:server-header:
+ *
+ * If non-%NULL, the value to use for the "Server" header on
+ * #SoupMessage<!-- -->s processed by this server.
+ *
+ * The Server header is the server equivalent of the
+ * User-Agent header, and provides information about the
+ * server and its components. It contains a list of one or
+ * more product tokens, separated by whitespace, with the most
+ * significant product token coming first. The tokens must be
+ * brief, ASCII, and mostly alphanumeric (although "-", "_",
+ * and "." are also allowed), and may optionally include a "/"
+ * followed by a version string. You may also put comments,
+ * enclosed in parentheses, between or after the tokens.
+ *
+ * Some HTTP server implementations intentionally do not use
+ * version numbers in their Server header, so that
+ * installations running older versions of the server don't
+ * end up advertising their vulnerability to specific security
+ * holes.
+ *
+ * As with #SoupSession:user_agent, if you set a
+ * #SoupServer:server_header property that has trailing whitespace,
+ * #SoupServer will append its own product token (eg,
+ * "<literal>libsoup/2.3.2</literal>") to the end of the
+ * header for you.
+ **/
+ /**
+ * SOUP_SERVER_SERVER_HEADER:
+ *
+ * Alias for the #SoupServer:server-header property, qv.
+ **/
+ g_object_class_install_property (
+ object_class, PROP_SERVER_HEADER,
+ g_param_spec_string (SOUP_SERVER_SERVER_HEADER,
+ "Server header",
+ "Server header",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SoupServer:http-aliases:
+ *
+ * A %NULL-terminated array of URI schemes that should be
+ * considered to be aliases for "http". Eg, if this included
+ * <literal>"dav"</literal>, than a URI of
+ * <literal>dav://example.com/path</literal> would be treated
+ * identically to <literal>http://example.com/path</literal>.
+ * In particular, this is needed in cases where a client
+ * sends requests with absolute URIs, where those URIs do
+ * not use "http:".
+ *
+ * The default value is an array containing the single element
+ * <literal>"*"</literal>, a special value which means that
+ * any scheme except "https" is considered to be an alias for
+ * "http".
+ *
+ * See also #SoupServer:https-aliases.
+ *
+ * Since: 2.44
+ */
+ /**
+ * SOUP_SERVER_HTTP_ALIASES:
+ *
+ * Alias for the #SoupServer:http-aliases property, qv.
+ *
+ * Since: 2.44
+ */
+ g_object_class_install_property (
+ object_class, PROP_HTTP_ALIASES,
+ g_param_spec_boxed (SOUP_SERVER_HTTP_ALIASES,
+ "http aliases",
+ "URI schemes that are considered aliases for 'http'",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ /**
+ * SoupServer:https-aliases:
+ *
+ * A comma-delimited list of URI schemes that should be
+ * considered to be aliases for "https". See
+ * #SoupServer:http-aliases for more information.
+ *
+ * The default value is %NULL, meaning that no URI schemes
+ * are considered aliases for "https".
+ *
+ * Since: 2.44
+ */
+ /**
+ * SOUP_SERVER_HTTPS_ALIASES:
+ *
+ * Alias for the #SoupServer:https-aliases property, qv.
+ *
+ * Since: 2.44
+ **/
+ g_object_class_install_property (
+ object_class, PROP_HTTPS_ALIASES,
+ g_param_spec_boxed (SOUP_SERVER_HTTPS_ALIASES,
+ "https aliases",
+ "URI schemes that are considered aliases for 'https'",
+ G_TYPE_STRV,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * SoupServer:add-websocket-extension: (skip)
+ *
+ * Add support for #SoupWebsocketExtension of the given type.
+ * (Shortcut for calling soup_server_add_websocket_extension().)
+ *
+ * Since: 2.68
+ **/
+ /**
+ * SOUP_SERVER_ADD_WEBSOCKET_EXTENSION: (skip)
+ *
+ * Alias for the #SoupServer:add-websocket-extension property, qv.
+ *
+ * Since: 2.68
+ **/
+ g_object_class_install_property (
+ object_class, PROP_ADD_WEBSOCKET_EXTENSION,
+ g_param_spec_gtype (SOUP_SERVER_ADD_WEBSOCKET_EXTENSION,
+ "Add support for a WebSocket extension",
+ "Add support for a WebSocket extension of the given type",
+ SOUP_TYPE_WEBSOCKET_EXTENSION,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+ /**
+ * SoupServer:remove-websocket-extension: (skip)
+ *
+ * Remove support for #SoupWebsocketExtension of the given type. (Shortcut for
+ * calling soup_server_remove_websocket_extension().)
+ *
+ * Since: 2.68
+ **/
+ /**
+ * SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION: (skip)
+ *
+ * Alias for the #SoupServer:remove-websocket-extension property, qv.
+ *
+ * Since: 2.68
+ **/
+ g_object_class_install_property (
+ object_class, PROP_REMOVE_WEBSOCKET_EXTENSION,
+ g_param_spec_gtype (SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION,
+ "Remove support for a WebSocket extension",
+ "Remove support for a WebSocket extension of the given type",
+ SOUP_TYPE_WEBSOCKET_EXTENSION,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * soup_server_new:
+ * @optname1: name of first property to set
+ * @...: value of @optname1, followed by additional property/value pairs
+ *
+ * Creates a new #SoupServer. This is exactly equivalent to calling
+ * g_object_new() and specifying %SOUP_TYPE_SERVER as the type.
+ *
+ * Return value: (nullable): a new #SoupServer. If you are using
+ * certain legacy properties, this may also return %NULL if an error
+ * occurs.
+ **/
+SoupServer *
+soup_server_new (const char *optname1, ...)
+{
+ SoupServer *server;
+ va_list ap;
+
+ va_start (ap, optname1);
+ server = (SoupServer *)g_object_new_valist (SOUP_TYPE_SERVER,
+ optname1, ap);
+ va_end (ap);
+
+ return server;
+}
+
+/**
+ * soup_server_set_ssl_cert_file:
+ * @server: a #SoupServer
+ * @ssl_cert_file: path to a file containing a PEM-encoded SSL/TLS
+ * certificate.
+ * @ssl_key_file: path to a file containing a PEM-encoded private key.
+ * @error: return location for a #GError
+ *
+ * Sets @server up to do https, using the SSL/TLS certificate
+ * specified by @ssl_cert_file and @ssl_key_file (which may point to
+ * the same file).
+ *
+ * Alternatively, you can set the #SoupServer:tls-certificate property
+ * at construction time, if you already have a #GTlsCertificate.
+ *
+ * Return value: success or failure.
+ *
+ * Since: 2.48
+ */
+gboolean
+soup_server_set_ssl_cert_file (SoupServer *server,
+ const char *ssl_cert_file,
+ const char *ssl_key_file,
+ GError **error)
+{
+ SoupServerPrivate *priv;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), FALSE);
+ priv = soup_server_get_instance_private (server);
+
+ if (priv->tls_cert)
+ g_object_unref (priv->tls_cert);
+
+ priv->tls_cert = g_tls_certificate_new_from_files (ssl_cert_file,
+ ssl_key_file,
+ error);
+ return priv->tls_cert != NULL;
+}
+
+/**
+ * soup_server_is_https:
+ * @server: a #SoupServer
+ *
+ * Checks whether @server is capable of https.
+ *
+ * In order for a server to run https, you must call
+ * soup_server_set_ssl_cert_file(), or set the
+ * #SoupServer:tls-certificate property, to provide it with a
+ * certificate to use.
+ *
+ * If you are using the deprecated single-listener APIs, then a return
+ * value of %TRUE indicates that the #SoupServer serves https
+ * exclusively. If you are using soup_server_listen(), etc, then a
+ * %TRUE return value merely indicates that the server is
+ * <emphasis>able</emphasis> to do https, regardless of whether it
+ * actually currently is or not. Use soup_server_get_uris() to see if
+ * it currently has any https listeners.
+ *
+ * Return value: %TRUE if @server is configured to serve https.
+ **/
+gboolean
+soup_server_is_https (SoupServer *server)
+{
+ SoupServerPrivate *priv;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), 0);
+ priv = soup_server_get_instance_private (server);
+
+ return priv->tls_cert != NULL;
+}
+
+/**
+ * soup_server_get_listeners:
+ * @server: a #SoupServer
+ *
+ * Gets @server's list of listening sockets.
+ *
+ * You should treat these sockets as read-only; writing to or
+ * modifiying any of these sockets may cause @server to malfunction.
+ *
+ * Return value: (transfer container) (element-type Gio.Socket): a
+ * list of listening sockets.
+ **/
+GSList *
+soup_server_get_listeners (SoupServer *server)
+{
+ SoupServerPrivate *priv;
+ GSList *listeners, *iter;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), NULL);
+ priv = soup_server_get_instance_private (server);
+
+ listeners = NULL;
+ for (iter = priv->listeners; iter; iter = iter->next)
+ listeners = g_slist_prepend (listeners, soup_socket_get_gsocket (iter->data));
+
+ /* priv->listeners has the sockets in reverse order from how
+ * they were added, so listeners now has them back in the
+ * original order.
+ */
+ return listeners;
+}
+
+static void start_request (SoupServer *, SoupClientContext *);
+static void socket_disconnected (SoupSocket *sock, SoupClientContext *client);
+
+static SoupClientContext *
+soup_client_context_new (SoupServer *server, SoupSocket *sock)
+{
+ SoupClientContext *client = g_slice_new0 (SoupClientContext);
+
+ client->server = server;
+ client->sock = g_object_ref (sock);
+ client->gsock = soup_socket_get_gsocket (sock);
+ if (client->gsock)
+ g_object_ref (client->gsock);
+ g_signal_connect (sock, "disconnected",
+ G_CALLBACK (socket_disconnected), client);
+ client->ref_count = 1;
+
+ return client;
+}
+
+static void
+soup_client_context_cleanup (SoupClientContext *client)
+{
+ g_clear_object (&client->auth_domain);
+ g_clear_pointer (&client->auth_user, g_free);
+ g_clear_object (&client->remote_addr);
+ g_clear_object (&client->local_addr);
+
+ client->msg = NULL;
+}
+
+static SoupClientContext *
+soup_client_context_ref (SoupClientContext *client)
+{
+ g_atomic_int_inc (&client->ref_count);
+ return client;
+}
+
+static void
+soup_client_context_unref (SoupClientContext *client)
+{
+ if (!g_atomic_int_dec_and_test (&client->ref_count))
+ return;
+
+ soup_client_context_cleanup (client);
+
+ g_signal_handlers_disconnect_by_func (client->sock, socket_disconnected, client);
+ g_object_unref (client->sock);
+ g_clear_object (&client->gsock);
+ g_clear_pointer (&client->remote_ip, g_free);
+ g_slice_free (SoupClientContext, client);
+}
+
+static void
+request_finished (SoupMessage *msg, SoupMessageIOCompletion completion, gpointer user_data)
+{
+ SoupClientContext *client = user_data;
+ SoupServer *server = client->server;
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ SoupSocket *sock = client->sock;
+ gboolean failed;
+
+ if (completion == SOUP_MESSAGE_IO_STOLEN) {
+ soup_client_context_unref (client);
+ g_object_unref (msg);
+ return;
+ }
+
+ /* Complete the message, assuming it actually really started. */
+ if (msg->method) {
+ soup_message_finished (msg);
+
+ failed = (completion == SOUP_MESSAGE_IO_INTERRUPTED ||
+ msg->status_code == SOUP_STATUS_IO_ERROR);
+ g_signal_emit (server,
+ failed ? signals[REQUEST_ABORTED] : signals[REQUEST_FINISHED],
+ 0, msg, client);
+ }
+
+ if (completion == SOUP_MESSAGE_IO_COMPLETE &&
+ soup_socket_is_connected (sock) &&
+ soup_message_is_keepalive (msg) &&
+ priv->listeners) {
+ start_request (server, client);
+ } else {
+ soup_socket_disconnect (client->sock);
+ soup_client_context_unref (client);
+ }
+ g_object_unref (msg);
+}
+
+/* "" was never documented as meaning the same thing as "/", but it
+ * effectively was. We have to special case it now or otherwise it
+ * would match "*" too.
+ */
+#define NORMALIZED_PATH(path) ((path) && *(path) ? (path) : "/")
+
+static SoupServerHandler *
+get_handler (SoupServer *server, SoupMessage *msg)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ SoupURI *uri;
+
+ uri = soup_message_get_uri (msg);
+ return soup_path_map_lookup (priv->handlers, NORMALIZED_PATH (uri->path));
+}
+
+static void
+call_handler (SoupServer *server, SoupServerHandler *handler,
+ SoupClientContext *client, SoupMessage *msg,
+ gboolean early)
+{
+ GHashTable *form_data_set;
+ SoupURI *uri;
+
+ if (early && !handler->early_callback)
+ return;
+ else if (!early && !handler->callback)
+ return;
+
+ if (msg->status_code != 0)
+ return;
+
+ uri = soup_message_get_uri (msg);
+ if (uri->query)
+ form_data_set = soup_form_decode (uri->query);
+ else
+ form_data_set = NULL;
+
+ if (early) {
+ (*handler->early_callback) (server, msg,
+ uri->path, form_data_set,
+ client, handler->early_user_data);
+ } else {
+ (*handler->callback) (server, msg,
+ uri->path, form_data_set,
+ client, handler->user_data);
+ }
+
+ if (form_data_set)
+ g_hash_table_unref (form_data_set);
+}
+
+static void
+got_headers (SoupMessage *msg, SoupClientContext *client)
+{
+ SoupServer *server = client->server;
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ SoupServerHandler *handler;
+ SoupURI *uri;
+ GDateTime *date;
+ char *date_string;
+ SoupAuthDomain *domain;
+ GSList *iter;
+ gboolean rejected = FALSE;
+ char *auth_user;
+
+ /* Add required response headers */
+ date = g_date_time_new_now_utc ();
+ date_string = soup_date_time_to_string (date, SOUP_DATE_HTTP);
+ soup_message_headers_replace (msg->response_headers, "Date",
+ date_string);
+ g_free (date_string);
+ g_date_time_unref (date);
+
+ if (msg->status_code != 0)
+ return;
+
+ uri = soup_message_get_uri (msg);
+ if ((soup_socket_is_ssl (client->sock) && !soup_uri_is_https (uri, priv->https_aliases)) ||
+ (!soup_socket_is_ssl (client->sock) && !soup_uri_is_http (uri, priv->http_aliases))) {
+ soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
+ return;
+ }
+
+ if (!priv->raw_paths) {
+ char *decoded_path;
+
+ decoded_path = soup_uri_decode (uri->path);
+
+ if (strstr (decoded_path, "/../") ||
+ g_str_has_suffix (decoded_path, "/..")
+#ifdef G_OS_WIN32
+ ||
+ strstr (decoded_path, "\\..\\") ||
+ strstr (decoded_path, "/..\\") ||
+ strstr (decoded_path, "\\../") ||
+ g_str_has_suffix (decoded_path, "\\..")
+#endif
+ ) {
+ /* Introducing new ".." segments is not allowed */
+ g_free (decoded_path);
+ soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
+ return;
+ }
+
+ soup_uri_set_path (uri, decoded_path);
+ g_free (decoded_path);
+ }
+
+ /* Now handle authentication. (We do this here so that if
+ * the request uses "Expect: 100-continue", we can reject it
+ * immediately rather than waiting for the request body to
+ * be sent.
+ */
+ for (iter = priv->auth_domains; iter; iter = iter->next) {
+ domain = iter->data;
+
+ if (soup_auth_domain_covers (domain, msg)) {
+ auth_user = soup_auth_domain_accepts (domain, msg);
+ if (auth_user) {
+ client->auth_domain = g_object_ref (domain);
+ client->auth_user = auth_user;
+ return;
+ }
+
+ rejected = TRUE;
+ }
+ }
+
+ /* If any auth domain rejected it, then it will need authentication. */
+ if (rejected) {
+ for (iter = priv->auth_domains; iter; iter = iter->next) {
+ domain = iter->data;
+
+ if (soup_auth_domain_covers (domain, msg))
+ soup_auth_domain_challenge (domain, msg);
+ }
+ return;
+ }
+
+ /* Otherwise, call the early handlers. */
+ handler = get_handler (server, msg);
+ if (handler)
+ call_handler (server, handler, client, msg, TRUE);
+}
+
+static void
+complete_websocket_upgrade (SoupMessage *msg, gpointer user_data)
+{
+ SoupClientContext *client = user_data;
+ SoupServer *server = client->server;
+ SoupURI *uri = soup_message_get_uri (msg);
+ SoupServerHandler *handler;
+ GIOStream *stream;
+ SoupWebsocketConnection *conn;
+
+ handler = get_handler (server, msg);
+ if (!handler || !handler->websocket_callback)
+ return;
+
+ soup_client_context_ref (client);
+ stream = soup_client_context_steal_connection (client);
+ conn = soup_websocket_connection_new_with_extensions (stream, uri,
+ SOUP_WEBSOCKET_CONNECTION_SERVER,
+ soup_message_headers_get_one (msg->request_headers, "Origin"),
+ soup_message_headers_get_one (msg->response_headers, "Sec-WebSocket-Protocol"),
+ handler->websocket_extensions);
+ handler->websocket_extensions = NULL;
+ g_object_unref (stream);
+ soup_client_context_unref (client);
+
+ (*handler->websocket_callback) (server, conn, uri->path, client,
+ handler->websocket_user_data);
+ g_object_unref (conn);
+ soup_client_context_unref (client);
+}
+
+static void
+got_body (SoupMessage *msg, SoupClientContext *client)
+{
+ SoupServer *server = client->server;
+ SoupServerHandler *handler;
+
+ g_signal_emit (server, signals[REQUEST_READ], 0, msg, client);
+
+ if (msg->status_code != 0)
+ return;
+
+ handler = get_handler (server, msg);
+ if (!handler) {
+ soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+ return;
+ }
+
+ call_handler (server, handler, client, msg, FALSE);
+ if (msg->status_code != 0)
+ return;
+
+ if (handler->websocket_callback) {
+ SoupServerPrivate *priv;
+
+ priv = soup_server_get_instance_private (server);
+ if (soup_websocket_server_process_handshake_with_extensions (msg,
+ handler->websocket_origin,
+ handler->websocket_protocols,
+ priv->websocket_extension_types,
+ &handler->websocket_extensions)) {
+ g_signal_connect (msg, "wrote-informational",
+ G_CALLBACK (complete_websocket_upgrade),
+ soup_client_context_ref (client));
+ }
+ }
+}
+
+static void
+start_request (SoupServer *server, SoupClientContext *client)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ SoupMessage *msg;
+
+ soup_client_context_cleanup (client);
+
+ /* Listen for another request on this connection */
+ msg = g_object_new (SOUP_TYPE_MESSAGE,
+ SOUP_MESSAGE_SERVER_SIDE, TRUE,
+ NULL);
+ client->msg = msg;
+
+ if (priv->server_header) {
+ soup_message_headers_append (msg->response_headers, "Server",
+ priv->server_header);
+ }
+
+ g_signal_connect (msg, "got_headers", G_CALLBACK (got_headers), client);
+ g_signal_connect (msg, "got_body", G_CALLBACK (got_body), client);
+
+ g_signal_emit (server, signals[REQUEST_STARTED], 0,
+ msg, client);
+
+ soup_message_read_request (msg, client->sock,
+ request_finished, client);
+}
+
+static void
+socket_disconnected (SoupSocket *sock, SoupClientContext *client)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (client->server);
+
+ priv->clients = g_slist_remove (priv->clients, client);
+
+ if (client->msg) {
+ soup_message_set_status (client->msg, SOUP_STATUS_IO_ERROR);
+ soup_message_io_finished (client->msg);
+ }
+}
+
+static void
+soup_server_accept_socket (SoupServer *server,
+ SoupSocket *sock)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ SoupClientContext *client;
+
+ client = soup_client_context_new (server, sock);
+ priv->clients = g_slist_prepend (priv->clients, client);
+ start_request (server, client);
+}
+
+/**
+ * soup_server_accept_iostream:
+ * @server: a #SoupServer
+ * @stream: a #GIOStream
+ * @local_addr: (allow-none): the local #GSocketAddress associated with the @stream
+ * @remote_addr: (allow-none): the remote #GSocketAddress associated with the @stream
+ * @error: return location for a #GError
+ *
+ * Add a new client stream to the @server.
+ *
+ * Return value: %TRUE on success, %FALSE if the stream could not be
+ * accepted or any other error occurred (in which case @error will be
+ * set).
+ *
+ * Since: 2.50
+ **/
+gboolean
+soup_server_accept_iostream (SoupServer *server,
+ GIOStream *stream,
+ GSocketAddress *local_addr,
+ GSocketAddress *remote_addr,
+ GError **error)
+{
+ SoupSocket *sock;
+
+ sock = g_initable_new (SOUP_TYPE_SOCKET, NULL, error,
+ "iostream", stream,
+ SOUP_SOCKET_LOCAL_ADDRESS, local_addr,
+ SOUP_SOCKET_REMOTE_CONNECTABLE, remote_addr,
+ NULL);
+
+ if (!sock)
+ return FALSE;
+
+ soup_server_accept_socket (server, sock);
+ g_object_unref (sock);
+
+ return TRUE;
+}
+
+static void
+new_connection (SoupSocket *listener, SoupSocket *sock, gpointer user_data)
+{
+ SoupServer *server = user_data;
+
+ soup_server_accept_socket (server, sock);
+}
+
+/**
+ * soup_server_disconnect:
+ * @server: a #SoupServer
+ *
+ * Closes and frees @server's listening sockets.
+ *
+ * Note that if there are currently requests in progress on @server,
+ * that they will continue to be processed if @server's #GMainContext
+ * is still running.
+ *
+ * You can call soup_server_listen(), etc, after calling this function
+ * if you want to start listening again.
+ **/
+void
+soup_server_disconnect (SoupServer *server)
+{
+ SoupServerPrivate *priv;
+ GSList *listeners, *clients, *iter;
+ SoupSocket *listener;
+ SoupClientContext *client;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ priv = soup_server_get_instance_private (server);
+
+ clients = priv->clients;
+ priv->clients = NULL;
+ listeners = priv->listeners;
+ priv->listeners = NULL;
+
+ for (iter = clients; iter; iter = iter->next) {
+ client = iter->data;
+ soup_socket_disconnect (client->sock);
+ }
+ g_slist_free (clients);
+
+ for (iter = listeners; iter; iter = iter->next) {
+ listener = iter->data;
+ soup_socket_disconnect (listener);
+ g_object_unref (listener);
+ }
+ g_slist_free (listeners);
+}
+
+/**
+ * SoupServerListenOptions:
+ * @SOUP_SERVER_LISTEN_HTTPS: Listen for https connections rather
+ * than plain http.
+ * @SOUP_SERVER_LISTEN_IPV4_ONLY: Only listen on IPv4 interfaces.
+ * @SOUP_SERVER_LISTEN_IPV6_ONLY: Only listen on IPv6 interfaces.
+ *
+ * Options to pass to soup_server_listen(), etc.
+ *
+ * %SOUP_SERVER_LISTEN_IPV4_ONLY and %SOUP_SERVER_LISTEN_IPV6_ONLY
+ * only make sense with soup_server_listen_all() and
+ * soup_server_listen_local(), not plain soup_server_listen() (which
+ * simply listens on whatever kind of socket you give it). And you
+ * cannot specify both of them in a single call.
+ *
+ * Since: 2.48
+ */
+
+static gboolean
+soup_server_listen_internal (SoupServer *server, SoupSocket *listener,
+ SoupServerListenOptions options,
+ GError **error)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ gboolean is_listening;
+
+ if (options & SOUP_SERVER_LISTEN_HTTPS) {
+ if (!priv->tls_cert) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_ARGUMENT,
+ _("Can’t create a TLS server without a TLS certificate"));
+ return FALSE;
+ }
+
+ g_object_set (G_OBJECT (listener),
+ SOUP_SOCKET_SSL_CREDENTIALS, priv->tls_cert,
+ NULL);
+ }
+
+ g_object_get (G_OBJECT (listener),
+ SOUP_SOCKET_IS_SERVER, &is_listening,
+ NULL);
+ if (!is_listening) {
+ if (!soup_socket_listen_full (listener, error)) {
+ GInetSocketAddress *addr = soup_socket_get_local_address (listener);
+ GInetAddress *inet_addr = g_inet_socket_address_get_address (addr);
+ char *local_ip = g_inet_address_to_string (inet_addr);
+
+ g_prefix_error (error,
+ _("Could not listen on address %s, port %d: "),
+ local_ip,
+ g_inet_socket_address_get_port (addr));
+ g_free (local_ip);
+ return FALSE;
+ }
+ }
+
+ g_signal_connect (listener, "new_connection",
+ G_CALLBACK (new_connection), server);
+
+ /* Note: soup_server_listen_ipv4_ipv6() below relies on the
+ * fact that this does g_slist_prepend().
+ */
+ priv->listeners = g_slist_prepend (priv->listeners, g_object_ref (listener));
+ return TRUE;
+}
+
+/**
+ * soup_server_listen:
+ * @server: a #SoupServer
+ * @address: the address of the interface to listen on
+ * @options: listening options for this server
+ * @error: return location for a #GError
+ *
+ * This attempts to set up @server to listen for connections on
+ * @address.
+ *
+ * If @options includes %SOUP_SERVER_LISTEN_HTTPS, and @server has
+ * been configured for TLS, then @server will listen for https
+ * connections on this port. Otherwise it will listen for plain http.
+ *
+ * You may call this method (along with the other "listen" methods)
+ * any number of times on a server, if you want to listen on multiple
+ * ports, or set up both http and https service.
+ *
+ * After calling this method, @server will begin accepting and
+ * processing connections as soon as the appropriate #GMainContext is
+ * run.
+ *
+ * Note that #SoupServer never makes use of dual IPv4/IPv6 sockets; if
+ * @address is an IPv6 address, it will only accept IPv6 connections.
+ * You must configure IPv4 listening separately.
+ *
+ * Return value: %TRUE on success, %FALSE if @address could not be
+ * bound or any other error occurred (in which case @error will be
+ * set).
+ *
+ * Since: 2.48
+ **/
+gboolean
+soup_server_listen (SoupServer *server, GSocketAddress *address,
+ SoupServerListenOptions options,
+ GError **error)
+{
+ SoupServerPrivate *priv;
+ SoupSocket *listener;
+ gboolean success, ipv6_only;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), FALSE);
+ g_return_val_if_fail (!(options & SOUP_SERVER_LISTEN_IPV4_ONLY) &&
+ !(options & SOUP_SERVER_LISTEN_IPV6_ONLY), FALSE);
+
+ priv = soup_server_get_instance_private (server);
+ g_return_val_if_fail (priv->disposed == FALSE, FALSE);
+
+ ipv6_only = g_socket_address_get_family (address) == G_SOCKET_FAMILY_IPV6;
+ listener = soup_socket_new (SOUP_SOCKET_LOCAL_ADDRESS, address,
+ SOUP_SOCKET_IPV6_ONLY, ipv6_only,
+ NULL);
+
+ success = soup_server_listen_internal (server, listener, options, error);
+ g_object_unref (listener);
+
+ return success;
+}
+
+static gboolean
+soup_server_listen_ipv4_ipv6 (SoupServer *server,
+ GInetAddress *iaddr4,
+ GInetAddress *iaddr6,
+ guint port,
+ SoupServerListenOptions options,
+ GError **error)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ GSocketAddress *addr4, *addr6;
+ GError *my_error = NULL;
+ SoupSocket *v4sock;
+ guint v4port;
+
+ g_return_val_if_fail (iaddr4 != NULL || iaddr6 != NULL, FALSE);
+
+ options &= ~(SOUP_SERVER_LISTEN_IPV4_ONLY | SOUP_SERVER_LISTEN_IPV6_ONLY);
+
+ try_again:
+ if (iaddr4) {
+ addr4 = g_inet_socket_address_new (iaddr4, port);
+ if (!soup_server_listen (server, addr4, options, error)) {
+ g_object_unref (addr4);
+ return FALSE;
+ }
+ g_object_unref (addr4);
+
+ v4sock = priv->listeners->data;
+ v4port = g_inet_socket_address_get_port (soup_socket_get_local_address (v4sock));
+ } else {
+ v4sock = NULL;
+ v4port = port;
+ }
+
+ if (!iaddr6)
+ return TRUE;
+
+ addr6 = g_inet_socket_address_new (iaddr6, v4port);
+ if (soup_server_listen (server, addr6, options, &my_error)) {
+ g_object_unref (addr6);
+ return TRUE;
+ }
+ g_object_unref (addr6);
+
+ if (v4sock && g_error_matches (my_error, G_IO_ERROR,
+#if GLIB_CHECK_VERSION (2, 41, 0)
+ G_IO_ERROR_NOT_SUPPORTED
+#else
+ G_IO_ERROR_FAILED
+#endif
+ )) {
+ /* No IPv6 support, but IPV6_ONLY wasn't specified, so just
+ * ignore the failure.
+ */
+ g_error_free (my_error);
+ return TRUE;
+ }
+
+ if (v4sock) {
+ priv->listeners = g_slist_remove (priv->listeners, v4sock);
+ soup_socket_disconnect (v4sock);
+ g_object_unref (v4sock);
+ }
+
+ if (port == 0 && g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE)) {
+ /* The randomly-assigned IPv4 port was in use on the IPv6 side... Try again */
+ g_clear_error (&my_error);
+ goto try_again;
+ }
+
+ g_propagate_error (error, my_error);
+ return FALSE;
+}
+
+/**
+ * soup_server_listen_all:
+ * @server: a #SoupServer
+ * @port: the port to listen on, or 0
+ * @options: listening options for this server
+ * @error: return location for a #GError
+ *
+ * This attempts to set up @server to listen for connections on all
+ * interfaces on the system. (That is, it listens on the addresses
+ * <literal>0.0.0.0</literal> and/or <literal>::</literal>, depending
+ * on whether @options includes %SOUP_SERVER_LISTEN_IPV4_ONLY,
+ * %SOUP_SERVER_LISTEN_IPV6_ONLY, or neither.) If @port is specified,
+ * @server will listen on that port. If it is 0, @server will find an
+ * unused port to listen on. (In that case, you can use
+ * soup_server_get_uris() to find out what port it ended up choosing.)
+ *
+ * See soup_server_listen() for more details.
+ *
+ * Return value: %TRUE on success, %FALSE if @port could not be bound
+ * or any other error occurred (in which case @error will be set).
+ *
+ * Since: 2.48
+ **/
+gboolean
+soup_server_listen_all (SoupServer *server, guint port,
+ SoupServerListenOptions options,
+ GError **error)
+{
+ GInetAddress *iaddr4, *iaddr6;
+ gboolean success;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), FALSE);
+ g_return_val_if_fail (!(options & SOUP_SERVER_LISTEN_IPV4_ONLY) ||
+ !(options & SOUP_SERVER_LISTEN_IPV6_ONLY), FALSE);
+
+ if (options & SOUP_SERVER_LISTEN_IPV6_ONLY)
+ iaddr4 = NULL;
+ else
+ iaddr4 = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
+
+ if (options & SOUP_SERVER_LISTEN_IPV4_ONLY)
+ iaddr6 = NULL;
+ else
+ iaddr6 = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6);
+
+ success = soup_server_listen_ipv4_ipv6 (server, iaddr4, iaddr6,
+ port, options, error);
+
+ g_clear_object (&iaddr4);
+ g_clear_object (&iaddr6);
+
+ return success;
+}
+
+/**
+ * soup_server_listen_local:
+ * @server: a #SoupServer
+ * @port: the port to listen on, or 0
+ * @options: listening options for this server
+ * @error: return location for a #GError
+ *
+ * This attempts to set up @server to listen for connections on
+ * "localhost" (that is, <literal>127.0.0.1</literal> and/or
+ * <literal>::1</literal>, depending on whether @options includes
+ * %SOUP_SERVER_LISTEN_IPV4_ONLY, %SOUP_SERVER_LISTEN_IPV6_ONLY, or
+ * neither). If @port is specified, @server will listen on that port.
+ * If it is 0, @server will find an unused port to listen on. (In that
+ * case, you can use soup_server_get_uris() to find out what port it
+ * ended up choosing.)
+ *
+ * See soup_server_listen() for more details.
+ *
+ * Return value: %TRUE on success, %FALSE if @port could not be bound
+ * or any other error occurred (in which case @error will be set).
+ *
+ * Since: 2.48
+ **/
+gboolean
+soup_server_listen_local (SoupServer *server, guint port,
+ SoupServerListenOptions options,
+ GError **error)
+{
+ GInetAddress *iaddr4, *iaddr6;
+ gboolean success;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), FALSE);
+ g_return_val_if_fail (!(options & SOUP_SERVER_LISTEN_IPV4_ONLY) ||
+ !(options & SOUP_SERVER_LISTEN_IPV6_ONLY), FALSE);
+
+ if (options & SOUP_SERVER_LISTEN_IPV6_ONLY)
+ iaddr4 = NULL;
+ else
+ iaddr4 = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV4);
+
+ if (options & SOUP_SERVER_LISTEN_IPV4_ONLY)
+ iaddr6 = NULL;
+ else
+ iaddr6 = g_inet_address_new_loopback (G_SOCKET_FAMILY_IPV6);
+
+ success = soup_server_listen_ipv4_ipv6 (server, iaddr4, iaddr6,
+ port, options, error);
+
+ g_clear_object (&iaddr4);
+ g_clear_object (&iaddr6);
+
+ return success;
+}
+
+/**
+ * soup_server_listen_socket:
+ * @server: a #SoupServer
+ * @socket: a listening #GSocket
+ * @options: listening options for this server
+ * @error: return location for a #GError
+ *
+ * This attempts to set up @server to listen for connections on
+ * @socket.
+ *
+ * See soup_server_listen() for more details.
+ *
+ * Return value: %TRUE on success, %FALSE if an error occurred (in
+ * which case @error will be set).
+ *
+ * Since: 2.48
+ **/
+gboolean
+soup_server_listen_socket (SoupServer *server, GSocket *socket,
+ SoupServerListenOptions options,
+ GError **error)
+{
+ SoupServerPrivate *priv;
+ SoupSocket *listener;
+ gboolean success;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), FALSE);
+ g_return_val_if_fail (G_IS_SOCKET (socket), FALSE);
+ g_return_val_if_fail (!(options & SOUP_SERVER_LISTEN_IPV4_ONLY) &&
+ !(options & SOUP_SERVER_LISTEN_IPV6_ONLY), FALSE);
+
+ priv = soup_server_get_instance_private (server);
+ g_return_val_if_fail (priv->disposed == FALSE, FALSE);
+
+ listener = g_initable_new (SOUP_TYPE_SOCKET, NULL, error,
+ SOUP_SOCKET_GSOCKET, socket,
+ SOUP_SOCKET_IPV6_ONLY, TRUE,
+ NULL);
+ if (!listener)
+ return FALSE;
+
+ success = soup_server_listen_internal (server, listener, options, error);
+ g_object_unref (listener);
+
+ return success;
+}
+
+/**
+ * soup_server_get_uris:
+ * @server: a #SoupServer
+ *
+ * Gets a list of URIs corresponding to the interfaces @server is
+ * listening on. These will contain IP addresses, not hostnames, and
+ * will also indicate whether the given listener is http or https.
+ *
+ * Note that if you used soup_server_listen_all(), the returned URIs
+ * will use the addresses <literal>0.0.0.0</literal> and
+ * <literal>::</literal>, rather than actually returning separate URIs
+ * for each interface on the system.
+ *
+ * Return value: (transfer full) (element-type Soup.URI): a list of
+ * #SoupURIs, which you must free when you are done with it.
+ *
+ * Since: 2.48
+ */
+GSList *
+soup_server_get_uris (SoupServer *server)
+{
+ SoupServerPrivate *priv;
+ GSList *uris, *l;
+ SoupSocket *listener;
+ GInetSocketAddress *addr;
+ GInetAddress *inet_addr;
+ char *ip;
+ SoupURI *uri;
+ gpointer creds;
+
+ g_return_val_if_fail (SOUP_IS_SERVER (server), NULL);
+ priv = soup_server_get_instance_private (server);
+
+ for (l = priv->listeners, uris = NULL; l; l = l->next) {
+ listener = l->data;
+ addr = soup_socket_get_local_address (listener);
+ inet_addr = g_inet_socket_address_get_address (addr);
+ ip = g_inet_address_to_string (inet_addr);
+ g_object_get (G_OBJECT (listener), SOUP_SOCKET_SSL_CREDENTIALS, &creds, NULL);
+
+ uri = soup_uri_new (NULL);
+ soup_uri_set_scheme (uri, creds ? "https" : "http");
+ soup_uri_set_host (uri, ip);
+ soup_uri_set_port (uri, g_inet_socket_address_get_port (addr));
+ soup_uri_set_path (uri, "/");
+
+ uris = g_slist_prepend (uris, uri);
+
+ g_free (ip);
+ }
+
+ return uris;
+}
+
+/**
+ * SoupClientContext:
+ *
+ * A #SoupClientContext provides additional information about the
+ * client making a particular request. In particular, you can use
+ * soup_client_context_get_auth_domain() and
+ * soup_client_context_get_auth_user() to determine if HTTP
+ * authentication was used successfully.
+ *
+ * soup_client_context_get_remote_address() and/or
+ * soup_client_context_get_host() can be used to get information for
+ * logging or debugging purposes. soup_client_context_get_socket() may
+ * also be of use in some situations (eg, tracking when multiple
+ * requests are made on the same connection).
+ **/
+G_DEFINE_BOXED_TYPE (SoupClientContext, soup_client_context, soup_client_context_ref, soup_client_context_unref)
+
+/**
+ * soup_client_context_get_soup_socket:
+ * @client: a #SoupClientContext
+ *
+ * Retrieves the #SoupSocket that @client 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 (either by using weak references, or by
+ * connecting to the #SoupSocket::disconnected signal), 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: (transfer none): the #SoupSocket that @client is
+ * associated with.
+ **/
+SoupSocket *
+soup_client_context_get_soup_socket (SoupClientContext *client)
+{
+ g_return_val_if_fail (client != NULL, NULL);
+
+ return client->sock;
+}
+
+/**
+ * soup_client_context_get_socket:
+ * @client: a #SoupClientContext
+ *
+ * Retrieves the #GSocket that @client 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 @client is
+ * associated with, %NULL if you used soup_server_accept_iostream().
+ *
+ * Since: 2.48
+ **/
+GSocket *
+soup_client_context_get_socket (SoupClientContext *client)
+{
+ g_return_val_if_fail (client != NULL, NULL);
+
+ return client->gsock;
+}
+
+/**
+ * soup_client_context_get_remote_address:
+ * @client: a #SoupClientContext
+ *
+ * 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().
+ *
+ * Since: 2.48
+ **/
+GSocketAddress *
+soup_client_context_get_remote_address (SoupClientContext *client)
+{
+ g_return_val_if_fail (client != NULL, NULL);
+
+ if (client->remote_addr)
+ return client->remote_addr;
+
+ client->remote_addr = client->gsock ?
+ g_socket_get_remote_address (client->gsock, NULL) :
+ G_SOCKET_ADDRESS (g_object_ref (soup_socket_get_remote_address (client->sock)));
+
+ return client->remote_addr;
+}
+
+/**
+ * soup_client_context_get_local_address:
+ * @client: a #SoupClientContext
+ *
+ * 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().
+ *
+ * Since: 2.48
+ **/
+GSocketAddress *
+soup_client_context_get_local_address (SoupClientContext *client)
+{
+ g_return_val_if_fail (client != NULL, NULL);
+
+ if (client->local_addr)
+ return client->local_addr;
+
+ client->local_addr = client->gsock ?
+ g_socket_get_local_address (client->gsock, NULL) :
+ G_SOCKET_ADDRESS (g_object_ref (soup_socket_get_local_address (client->sock)));
+
+ return client->local_addr;
+}
+
+/**
+ * soup_client_context_get_host:
+ * @client: a #SoupClientContext
+ *
+ * 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_client_context_get_host (SoupClientContext *client)
+{
+ g_return_val_if_fail (client != NULL, NULL);
+
+ if (client->remote_ip)
+ return client->remote_ip;
+
+ if (client->gsock) {
+ GSocketAddress *addr = soup_client_context_get_remote_address (client);
+ GInetAddress *iaddr;
+
+ if (!addr || !G_IS_INET_SOCKET_ADDRESS (addr))
+ return NULL;
+ iaddr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (addr));
+ client->remote_ip = g_inet_address_to_string (iaddr);
+ } else {
+ GInetSocketAddress *addr = G_INET_SOCKET_ADDRESS (soup_socket_get_remote_address (client->sock));
+ GInetAddress *inet_addr = g_inet_socket_address_get_address (addr);
+ client->remote_ip = g_inet_address_to_string (inet_addr);
+ }
+
+ return client->remote_ip;
+}
+
+/**
+ * soup_client_context_get_auth_domain:
+ * @client: a #SoupClientContext
+ *
+ * Checks whether the request associated with @client has been
+ * authenticated, and if so returns the #SoupAuthDomain that
+ * authenticated it.
+ *
+ * Return value: (transfer none) (nullable): a #SoupAuthDomain, or
+ * %NULL if the request was not authenticated.
+ **/
+SoupAuthDomain *
+soup_client_context_get_auth_domain (SoupClientContext *client)
+{
+ g_return_val_if_fail (client != NULL, NULL);
+
+ return client->auth_domain;
+}
+
+/**
+ * soup_client_context_get_auth_user:
+ * @client: a #SoupClientContext
+ *
+ * Checks whether the request associated with @client has been
+ * authenticated, and if so returns the username that the client
+ * authenticated as.
+ *
+ * Return value: (nullable): the authenticated-as user, or %NULL if
+ * the request was not authenticated.
+ **/
+const char *
+soup_client_context_get_auth_user (SoupClientContext *client)
+{
+ g_return_val_if_fail (client != NULL, NULL);
+
+ return client->auth_user;
+}
+
+/**
+ * soup_client_context_steal_connection:
+ * @client: a #SoupClientContext
+ *
+ * "Steals" the HTTP connection associated with @client from its
+ * #SoupServer. This happens immediately, regardless of the current
+ * state of the connection; if the response to the current
+ * #SoupMessage has not yet finished being sent, then it will be
+ * discarded; you can steal the connection from a
+ * #SoupMessage::wrote-informational or #SoupMessage::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, @client will most
+ * likely be freed as a side effect.
+ *
+ * Return value: (transfer full): the #GIOStream formerly associated
+ * with @client (or %NULL if @client was no longer associated with a
+ * connection). No guarantees are made about what kind of #GIOStream
+ * is returned.
+ *
+ * Since: 2.50
+ **/
+GIOStream *
+soup_client_context_steal_connection (SoupClientContext *client)
+{
+ GIOStream *stream;
+
+ g_return_val_if_fail (client != NULL, NULL);
+
+ soup_client_context_ref (client);
+
+ stream = soup_message_io_steal (client->msg);
+ if (stream) {
+ g_object_set_data_full (G_OBJECT (stream), "GSocket",
+ soup_socket_steal_gsocket (client->sock),
+ g_object_unref);
+ }
+
+ socket_disconnected (client->sock, client);
+ soup_client_context_unref (client);
+
+ return stream;
+}
+
+
+/**
+ * SoupServerCallback:
+ * @server: the #SoupServer
+ * @msg: the message being processed
+ * @path: the path component of @msg's Request-URI
+ * @query: (element-type utf8 utf8) (allow-none): the parsed query
+ * component of @msg's Request-URI
+ * @client: additional contextual information about the client
+ * @user_data: the data passed to soup_server_add_handler() or
+ * soup_server_add_early_handler().
+ *
+ * A callback used to handle requests to a #SoupServer.
+ *
+ * @path and @query contain the likewise-named components of the
+ * Request-URI, subject to certain assumptions. By default,
+ * #SoupServer decodes all percent-encoding in the URI path, such that
+ * "/foo%<!-- -->2Fbar" is treated the same as "/foo/bar". If your
+ * server is serving resources in some non-POSIX-filesystem namespace,
+ * you may want to distinguish those as two distinct paths. In that
+ * case, you can set the %SOUP_SERVER_RAW_PATHS property when creating
+ * the #SoupServer, and it will leave those characters undecoded. (You
+ * may want to call soup_uri_normalize() to decode any percent-encoded
+ * characters that you aren't handling specially.)
+ *
+ * @query contains the query component of the Request-URI parsed
+ * according to the rules for HTML form handling. Although this is the
+ * only commonly-used query string format in HTTP, there is nothing
+ * that actually requires that HTTP URIs use that format; if your
+ * server needs to use some other format, you can just ignore @query,
+ * and call soup_message_get_uri() and parse the URI's query field
+ * yourself.
+ *
+ * See soup_server_add_handler() and soup_server_add_early_handler()
+ * for details of what handlers can/should do.
+ **/
+
+static SoupServerHandler *
+get_or_create_handler (SoupServer *server, const char *exact_path)
+{
+ SoupServerPrivate *priv = soup_server_get_instance_private (server);
+ SoupServerHandler *handler;
+
+ exact_path = NORMALIZED_PATH (exact_path);
+
+ handler = soup_path_map_lookup (priv->handlers, exact_path);
+ if (handler && !strcmp (handler->path, exact_path))
+ return handler;
+
+ handler = g_slice_new0 (SoupServerHandler);
+ handler->path = g_strdup (exact_path);
+ soup_path_map_add (priv->handlers, exact_path, handler);
+
+ return handler;
+}
+
+/**
+ * soup_server_add_handler:
+ * @server: a #SoupServer
+ * @path: (allow-none): the toplevel path for the handler
+ * @callback: callback to invoke for requests under @path
+ * @user_data: data for @callback
+ * @destroy: destroy notifier to free @user_data
+ *
+ * Adds a handler to @server for requests under @path. If @path is
+ * %NULL or "/", then this will be the default handler for all
+ * requests that don't have a more specific handler. (Note though that
+ * if you want to handle requests to the special "*" URI, you must
+ * explicitly register a handler for "*"; the default handler will not
+ * be used for that case.)
+ *
+ * For requests under @path (that have not already been assigned a
+ * status code by a #SoupAuthDomain, an early #SoupServerHandler, or a
+ * signal handler), @callback will be invoked after receiving the
+ * request body; the message's #SoupMessage:method,
+ * #SoupMessage:request-headers, and #SoupMessage:request-body fields
+ * will be filled in.
+ *
+ * After determining what to do with the request, the callback must at
+ * a minimum call soup_message_set_status() (or
+ * soup_message_set_status_full()) on the message to set the response
+ * status code. Additionally, it may set response headers and/or fill
+ * in the response body.
+ *
+ * If the callback cannot fully fill in the response before returning
+ * (eg, if it needs to wait for information from a database, or
+ * another network server), it should call soup_server_pause_message()
+ * to tell @server to not send the response right away. When the
+ * response is ready, call soup_server_unpause_message() to cause it
+ * to be sent.
+ *
+ * To send the response body a bit at a time using "chunked" encoding,
+ * first call soup_message_headers_set_encoding() to set
+ * %SOUP_ENCODING_CHUNKED on the #SoupMessage:response-headers. Then call
+ * soup_message_body_append() (or soup_message_body_append_bytes))
+ * to append each chunk as it becomes ready, and
+ * soup_server_unpause_message() to make sure it's running. (The
+ * server will automatically pause the message if it is using chunked
+ * encoding but no more chunks are available.) When you are done, call
+ * soup_message_body_complete() to indicate that no more chunks are
+ * coming.
+ **/
+void
+soup_server_add_handler (SoupServer *server,
+ const char *path,
+ SoupServerCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ SoupServerHandler *handler;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ g_return_if_fail (callback != NULL);
+
+ handler = get_or_create_handler (server, path);
+ if (handler->destroy)
+ handler->destroy (handler->user_data);
+
+ handler->callback = callback;
+ handler->destroy = destroy;
+ handler->user_data = user_data;
+}
+
+/**
+ * soup_server_add_early_handler:
+ * @server: a #SoupServer
+ * @path: (allow-none): the toplevel path for the handler
+ * @callback: callback to invoke for requests under @path
+ * @user_data: data for @callback
+ * @destroy: destroy notifier to free @user_data
+ *
+ * Adds an "early" handler to @server for requests under @path. Note
+ * that "normal" and "early" handlers are matched up together, so if
+ * you add a normal handler for "/foo" and an early handler for
+ * "/foo/bar", then a request to "/foo/bar" (or any path below it)
+ * will run only the early handler. (But if you add both handlers at
+ * the same path, then both will get run.)
+ *
+ * For requests under @path (that have not already been assigned a
+ * status code by a #SoupAuthDomain or a signal handler), @callback
+ * will be invoked after receiving the request headers, but before
+ * receiving the request body; the message's #SoupMessage:method and
+ * #SoupMessage:request-headers fields will be filled in.
+ *
+ * Early handlers are generally used for processing requests with
+ * request bodies in a streaming fashion. If you determine that the
+ * request will contain a message body, normally you would call
+ * soup_message_body_set_accumulate() on the message's
+ * #SoupMessage:request-body to turn off request-body accumulation,
+ * and connect to the message's #SoupMessage::got-chunk signal to
+ * process each chunk as it comes in.
+ *
+ * To complete the message processing after the full message body has
+ * been read, you can either also connect to #SoupMessage::got-body,
+ * or else you can register a non-early handler for @path as well. As
+ * long as you have not set the #SoupMessage:status-code by the time
+ * #SoupMessage::got-body is emitted, the non-early handler will be
+ * run as well.
+ *
+ * Since: 2.50
+ **/
+void
+soup_server_add_early_handler (SoupServer *server,
+ const char *path,
+ SoupServerCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ SoupServerHandler *handler;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ g_return_if_fail (callback != NULL);
+
+ handler = get_or_create_handler (server, path);
+ if (handler->early_destroy)
+ handler->early_destroy (handler->early_user_data);
+
+ handler->early_callback = callback;
+ handler->early_destroy = destroy;
+ handler->early_user_data = user_data;
+}
+
+/**
+ * SoupServerWebsocketCallback:
+ * @server: the #SoupServer
+ * @path: the path component of @msg's Request-URI
+ * @connection: the newly created WebSocket connection
+ * @client: additional contextual information about the client
+ * @user_data: the data passed to @soup_server_add_handler
+ *
+ * A callback used to handle WebSocket requests to a #SoupServer. The
+ * callback will be invoked after sending the handshake response back
+ * to the client (and is only invoked if the handshake was
+ * successful).
+ *
+ * @path contains the path of the Request-URI, subject to the same
+ * rules as #SoupServerCallback (qv).
+ **/
+
+/**
+ * soup_server_add_websocket_handler:
+ * @server: a #SoupServer
+ * @path: (allow-none): the toplevel path for the handler
+ * @origin: (allow-none): the origin of the connection
+ * @protocols: (allow-none) (array zero-terminated=1): the protocols
+ * supported by this handler
+ * @callback: callback to invoke for successful WebSocket requests under @path
+ * @user_data: data for @callback
+ * @destroy: destroy notifier to free @user_data
+ *
+ * Adds a WebSocket handler to @server for requests under @path. (If
+ * @path is %NULL or "/", then this will be the default handler for
+ * all requests that don't have a more specific handler.)
+ *
+ * When a path has a WebSocket handler registered, @server will check
+ * incoming requests for WebSocket handshakes after all other handlers
+ * have run (unless some earlier handler has already set a status code
+ * on the message), and update the request's status, response headers,
+ * and response body accordingly.
+ *
+ * If @origin is non-%NULL, then only requests containing a matching
+ * "Origin" header will be accepted. If @protocols is non-%NULL, then
+ * only requests containing a compatible "Sec-WebSocket-Protocols"
+ * header will be accepted. More complicated requirements can be
+ * handled by adding a normal handler to @path, and having it perform
+ * whatever checks are needed and
+ * setting a failure status code if the handshake should be rejected.
+ **/
+void
+soup_server_add_websocket_handler (SoupServer *server,
+ const char *path,
+ const char *origin,
+ char **protocols,
+ SoupServerWebsocketCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ SoupServerHandler *handler;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ g_return_if_fail (callback != NULL);
+
+ handler = get_or_create_handler (server, path);
+ if (handler->websocket_destroy)
+ handler->websocket_destroy (handler->websocket_user_data);
+ if (handler->websocket_origin)
+ g_free (handler->websocket_origin);
+ if (handler->websocket_protocols)
+ g_strfreev (handler->websocket_protocols);
+ g_list_free_full (handler->websocket_extensions, g_object_unref);
+
+ handler->websocket_callback = callback;
+ handler->websocket_destroy = destroy;
+ handler->websocket_user_data = user_data;
+ handler->websocket_origin = g_strdup (origin);
+ handler->websocket_protocols = g_strdupv (protocols);
+ handler->websocket_extensions = NULL;
+}
+
+/**
+ * soup_server_remove_handler:
+ * @server: a #SoupServer
+ * @path: the toplevel path for the handler
+ *
+ * Removes all handlers (early and normal) registered at @path.
+ **/
+void
+soup_server_remove_handler (SoupServer *server, const char *path)
+{
+ SoupServerPrivate *priv;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ priv = soup_server_get_instance_private (server);
+
+ soup_path_map_remove (priv->handlers, NORMALIZED_PATH (path));
+}
+
+/**
+ * soup_server_add_auth_domain:
+ * @server: a #SoupServer
+ * @auth_domain: a #SoupAuthDomain
+ *
+ * Adds an authentication domain to @server. Each auth domain will
+ * have the chance to require authentication for each request that
+ * comes in; normally auth domains will require authentication for
+ * requests on certain paths that they have been set up to watch, or
+ * that meet other criteria set by the caller. If an auth domain
+ * determines that a request requires authentication (and the request
+ * doesn't contain authentication), @server will automatically reject
+ * the request with an appropriate status (401 Unauthorized or 407
+ * Proxy Authentication Required). If the request used the
+ * "100-continue" Expectation, @server will reject it before the
+ * request body is sent.
+ **/
+void
+soup_server_add_auth_domain (SoupServer *server, SoupAuthDomain *auth_domain)
+{
+ SoupServerPrivate *priv;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ priv = soup_server_get_instance_private (server);
+
+ priv->auth_domains = g_slist_append (priv->auth_domains, auth_domain);
+ g_object_ref (auth_domain);
+}
+
+/**
+ * soup_server_remove_auth_domain:
+ * @server: a #SoupServer
+ * @auth_domain: a #SoupAuthDomain
+ *
+ * Removes @auth_domain from @server.
+ **/
+void
+soup_server_remove_auth_domain (SoupServer *server, SoupAuthDomain *auth_domain)
+{
+ SoupServerPrivate *priv;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ priv = soup_server_get_instance_private (server);
+
+ priv->auth_domains = g_slist_remove (priv->auth_domains, auth_domain);
+ g_object_unref (auth_domain);
+}
+
+/**
+ * soup_server_pause_message:
+ * @server: a #SoupServer
+ * @msg: a #SoupMessage associated with @server.
+ *
+ * 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
+ * soup_server_unpause_message() to resume I/O.
+ *
+ * This must only be called on a #SoupMessage which was created by the
+ * #SoupServer and are currently doing I/O, such as those passed into a
+ * #SoupServerCallback or emitted in a #SoupServer::request-read signal.
+ **/
+void
+soup_server_pause_message (SoupServer *server,
+ SoupMessage *msg)
+{
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ g_return_if_fail (SOUP_IS_MESSAGE (msg));
+
+ soup_message_io_pause (msg);
+}
+
+/**
+ * soup_server_unpause_message:
+ * @server: a #SoupServer
+ * @msg: a #SoupMessage associated with @server.
+ *
+ * Resumes I/O on @msg. Use this to resume after calling
+ * soup_server_pause_message(), or after adding a new chunk to a
+ * chunked response.
+ *
+ * I/O won't actually resume until you return to the main loop.
+ *
+ * This must only be called on a #SoupMessage which was created by the
+ * #SoupServer and are currently doing I/O, such as those passed into a
+ * #SoupServerCallback or emitted in a #SoupServer::request-read signal.
+ **/
+void
+soup_server_unpause_message (SoupServer *server,
+ SoupMessage *msg)
+{
+ g_return_if_fail (SOUP_IS_SERVER (server));
+ g_return_if_fail (SOUP_IS_MESSAGE (msg));
+
+ soup_message_io_unpause (msg);
+}
+
+/**
+ * soup_server_add_websocket_extension:
+ * @server: a #SoupServer
+ * @extension_type: a #GType
+ *
+ * Add support for a WebSocket extension of the given @extension_type.
+ * When a WebSocket client requests an extension of @extension_type,
+ * a new #SoupWebsocketExtension of type @extension_type will be created
+ * to handle the request.
+ *
+ * You can also add support for a WebSocket extension to the server at
+ * construct time by using the %SOUP_SERVER_ADD_WEBSOCKET_EXTENSION property.
+ * Note that #SoupWebsocketExtensionDeflate is supported by default, use
+ * soup_server_remove_websocket_extension() if you want to disable it.
+ *
+ * Since: 2.68
+ */
+void
+soup_server_add_websocket_extension (SoupServer *server, GType extension_type)
+{
+ SoupServerPrivate *priv;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+
+ priv = soup_server_get_instance_private (server);
+ if (!g_type_is_a (extension_type, SOUP_TYPE_WEBSOCKET_EXTENSION)) {
+ g_warning ("Type '%s' is not a SoupWebsocketExtension", g_type_name (extension_type));
+ return;
+ }
+
+ g_ptr_array_add (priv->websocket_extension_types, g_type_class_ref (extension_type));
+}
+
+/**
+ * soup_server_remove_websocket_extension:
+ * @server: a #SoupServer
+ * @extension_type: a #GType
+ *
+ * Removes support for WebSocket extension of type @extension_type (or any subclass of
+ * @extension_type) from @server. You can also remove extensions enabled by default
+ * from the server at construct time by using the %SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION
+ * property.
+ *
+ * Since: 2.68
+ */
+void
+soup_server_remove_websocket_extension (SoupServer *server, GType extension_type)
+{
+ SoupServerPrivate *priv;
+ SoupWebsocketExtensionClass *extension_class;
+ guint i;
+
+ g_return_if_fail (SOUP_IS_SERVER (server));
+
+ priv = soup_server_get_instance_private (server);
+ if (!g_type_is_a (extension_type, SOUP_TYPE_WEBSOCKET_EXTENSION)) {
+ g_warning ("Type '%s' is not a SoupWebsocketExtension", g_type_name (extension_type));
+ return;
+ }
+
+ extension_class = g_type_class_peek (extension_type);
+ for (i = 0; i < priv->websocket_extension_types->len; i++) {
+ if (priv->websocket_extension_types->pdata[i] == (gpointer)extension_class) {
+ g_ptr_array_remove_index (priv->websocket_extension_types, i);
+ break;
+ }
+ }
+}
diff --git a/libsoup/server/soup-server.h b/libsoup/server/soup-server.h
new file mode 100644
index 00000000..eb9a13b7
--- /dev/null
+++ b/libsoup/server/soup-server.h
@@ -0,0 +1,180 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2000-2003, Ximian, Inc.
+ */
+
+#pragma once
+
+#include "soup-types.h"
+#include "soup-uri.h"
+#include "soup-websocket-connection.h"
+
+G_BEGIN_DECLS
+
+#define SOUP_TYPE_SERVER (soup_server_get_type ())
+SOUP_AVAILABLE_IN_2_4
+G_DECLARE_DERIVABLE_TYPE (SoupServer, soup_server, SOUP, SERVER, GObject)
+
+typedef struct _SoupClientContext SoupClientContext;
+SOUP_AVAILABLE_IN_2_4
+GType soup_client_context_get_type (void);
+#define SOUP_TYPE_CLIENT_CONTEXT (soup_client_context_get_type ())
+
+typedef enum {
+ SOUP_SERVER_LISTEN_HTTPS = (1 << 0),
+ SOUP_SERVER_LISTEN_IPV4_ONLY = (1 << 1),
+ SOUP_SERVER_LISTEN_IPV6_ONLY = (1 << 2)
+} SoupServerListenOptions;
+
+struct _SoupServerClass {
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*request_started) (SoupServer *server, SoupMessage *msg,
+ SoupClientContext *client);
+ void (*request_read) (SoupServer *server, SoupMessage *msg,
+ SoupClientContext *client);
+ void (*request_finished) (SoupServer *server, SoupMessage *msg,
+ SoupClientContext *client);
+ void (*request_aborted) (SoupServer *server, SoupMessage *msg,
+ SoupClientContext *client);
+
+ gpointer padding[6];
+};
+
+#define SOUP_SERVER_TLS_CERTIFICATE "tls-certificate"
+#define SOUP_SERVER_RAW_PATHS "raw-paths"
+#define SOUP_SERVER_SERVER_HEADER "server-header"
+#define SOUP_SERVER_HTTP_ALIASES "http-aliases"
+#define SOUP_SERVER_HTTPS_ALIASES "https-aliases"
+
+SOUP_AVAILABLE_IN_2_4
+SoupServer *soup_server_new (const char *optname1,
+ ...) G_GNUC_NULL_TERMINATED;
+
+SOUP_AVAILABLE_IN_2_48
+gboolean soup_server_set_ssl_cert_file (SoupServer *server,
+ const char *ssl_cert_file,
+ const char *ssl_key_file,
+ GError **error);
+SOUP_AVAILABLE_IN_2_4
+gboolean soup_server_is_https (SoupServer *server);
+
+SOUP_AVAILABLE_IN_2_48
+gboolean soup_server_listen (SoupServer *server,
+ GSocketAddress *address,
+ SoupServerListenOptions options,
+ GError **error);
+SOUP_AVAILABLE_IN_2_48
+gboolean soup_server_listen_all (SoupServer *server,
+ guint port,
+ SoupServerListenOptions options,
+ GError **error);
+SOUP_AVAILABLE_IN_2_48
+gboolean soup_server_listen_local (SoupServer *server,
+ guint port,
+ SoupServerListenOptions options,
+ GError **error);
+SOUP_AVAILABLE_IN_2_48
+gboolean soup_server_listen_socket (SoupServer *server,
+ GSocket *socket,
+ SoupServerListenOptions options,
+ GError **error);
+SOUP_AVAILABLE_IN_2_48
+GSList *soup_server_get_uris (SoupServer *server);
+SOUP_AVAILABLE_IN_2_48
+GSList *soup_server_get_listeners (SoupServer *server);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_server_disconnect (SoupServer *server);
+
+SOUP_AVAILABLE_IN_2_50
+gboolean soup_server_accept_iostream (SoupServer *server,
+ GIOStream *stream,
+ GSocketAddress *local_addr,
+ GSocketAddress *remote_addr,
+ GError **error);
+
+/* Handlers and auth */
+
+typedef void (*SoupServerCallback) (SoupServer *server,
+ SoupMessage *msg,
+ const char *path,
+ GHashTable *query,
+ SoupClientContext *client,
+ gpointer user_data);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_server_add_handler (SoupServer *server,
+ const char *path,
+ SoupServerCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy);
+SOUP_AVAILABLE_IN_2_50
+void soup_server_add_early_handler (SoupServer *server,
+ const char *path,
+ SoupServerCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy);
+
+#define SOUP_SERVER_ADD_WEBSOCKET_EXTENSION "add-websocket-extension"
+#define SOUP_SERVER_REMOVE_WEBSOCKET_EXTENSION "remove-websocket-extension"
+
+typedef void (*SoupServerWebsocketCallback) (SoupServer *server,
+ SoupWebsocketConnection *connection,
+ const char *path,
+ SoupClientContext *client,
+ gpointer user_data);
+SOUP_AVAILABLE_IN_2_50
+void soup_server_add_websocket_handler (SoupServer *server,
+ const char *path,
+ const char *origin,
+ char **protocols,
+ SoupServerWebsocketCallback callback,
+ gpointer user_data,
+ GDestroyNotify destroy);
+SOUP_AVAILABLE_IN_2_68
+void soup_server_add_websocket_extension (SoupServer *server,
+ GType extension_type);
+SOUP_AVAILABLE_IN_2_68
+void soup_server_remove_websocket_extension (SoupServer *server,
+ GType extension_type);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_server_remove_handler (SoupServer *server,
+ const char *path);
+
+SOUP_AVAILABLE_IN_2_4
+void soup_server_add_auth_domain (SoupServer *server,
+ SoupAuthDomain *auth_domain);
+SOUP_AVAILABLE_IN_2_4
+void soup_server_remove_auth_domain (SoupServer *server,
+ SoupAuthDomain *auth_domain);
+
+/* I/O */
+SOUP_AVAILABLE_IN_2_4
+void soup_server_pause_message (SoupServer *server,
+ SoupMessage *msg);
+SOUP_AVAILABLE_IN_2_4
+void soup_server_unpause_message (SoupServer *server,
+ SoupMessage *msg);
+
+/* Client context */
+
+SOUP_AVAILABLE_IN_2_48
+GSocket *soup_client_context_get_socket (SoupClientContext *client);
+SOUP_AVAILABLE_IN_2_48
+GSocketAddress *soup_client_context_get_local_address (SoupClientContext *client);
+SOUP_AVAILABLE_IN_2_48
+GSocketAddress *soup_client_context_get_remote_address (SoupClientContext *client);
+SOUP_AVAILABLE_IN_2_4
+const char *soup_client_context_get_host (SoupClientContext *client);
+SOUP_AVAILABLE_IN_2_4
+SoupAuthDomain *soup_client_context_get_auth_domain (SoupClientContext *client);
+SOUP_AVAILABLE_IN_2_4
+const char *soup_client_context_get_auth_user (SoupClientContext *client);
+
+SOUP_AVAILABLE_IN_2_50
+GIOStream *soup_client_context_steal_connection (SoupClientContext *client);
+
+G_END_DECLS