summaryrefslogtreecommitdiff
path: root/libsoup/soup-hsts-policy.c
diff options
context:
space:
mode:
Diffstat (limited to 'libsoup/soup-hsts-policy.c')
-rw-r--r--libsoup/soup-hsts-policy.c438
1 files changed, 177 insertions, 261 deletions
diff --git a/libsoup/soup-hsts-policy.c b/libsoup/soup-hsts-policy.c
index e2989dbb..52c076b0 100644
--- a/libsoup/soup-hsts-policy.c
+++ b/libsoup/soup-hsts-policy.c
@@ -1,14 +1,16 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
- * soup-hsts-policy.c
+ * soup-hsts-policy.c: HSTS policy structure
*
- * Copyright (C) 2016 Igalia S.L.
+ * Copyright (C) 2016, 2017, 2018 Igalia S.L.
+ * Copyright (C) 2017, 2018 Metrological Group B.V.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -18,78 +20,80 @@
/**
* SECTION:soup-hsts-policy
* @short_description: HTTP Strict Transport Security policies
- * @see_also: #SoupHstsEnforcer
+ * @see_also: #SoupHSTSEnforcer
*
- * #SoupHstsPolicy implements HTTP policies, as described by <ulink
+ * #SoupHSTSPolicy implements HTTP policies, as described by <ulink
* url="http://tools.ietf.org/html/rfc6797">RFC 6797</ulink>.
*
* To have a #SoupSession handle HSTS policies for your appliction
- * automatically, use a #SoupHstsEnforcer.
+ * automatically, use a #SoupHSTSEnforcer.
**/
/**
- * SoupHstsPolicy:
- * @domain: the "domain" attribute, or else the hostname that the
- * policy came from.
- * @expires: the policy expiration time, or %NULL for a session policy
- * @include_sub_domains: %TRUE if the policy applies on sub domains
+ * SoupHSTSPolicy:
+ * @domain: The domain or hostname that the policy applies to
+ * @max_age: The maximum age, in seconds, that the policy is valid
+ * @expires: the policy expiration time, or %NULL for a permanent session policy
+ * @include_subdomains: %TRUE if the policy applies on subdomains
*
* An HTTP Strict Transport Security policy.
*
* @domain give the host or domain that this policy belongs to and applies
* on.
*
+ * @max_age contains the 'max-age' value from the Strict Transport
+ * Security header and indicates the time to live of this policy,
+ * in seconds.
+ *
* @expires will be non-%NULL if the policy has been set by the host and
* hence has an expiry time. If @expires is %NULL, it indicates that the
- * policy is a session policy set by the user agent.
- *
- * If @include_sub_domains is set, the strict transport security policy
- * must also be enforced on all subdomains of @domain.
+ * policy is a permanent session policy set by the user agent.
+ *
+ * If @include_subdomains is %TRUE, the Strict Transport Security policy
+ * must also be enforced on subdomains of @domain.
*
- * Since: 2.54
+ * Since: 2.64
**/
-G_DEFINE_BOXED_TYPE (SoupHstsPolicy, soup_hsts_policy, soup_hsts_policy_copy, soup_hsts_policy_free)
+G_DEFINE_BOXED_TYPE (SoupHSTSPolicy, soup_hsts_policy, soup_hsts_policy_copy, soup_hsts_policy_free)
/**
* soup_hsts_policy_copy:
- * @policy: a #SoupHstsPolicy
+ * @policy: a #SoupHSTSPolicy
*
* Copies @policy.
*
- * Return value: a copy of @policy
+ * Returns: (transfer full): a copy of @policy
*
- * Since: 2.54
+ * Since: 2.64
**/
-SoupHstsPolicy *
-soup_hsts_policy_copy (SoupHstsPolicy *policy)
+SoupHSTSPolicy *
+soup_hsts_policy_copy (SoupHSTSPolicy *policy)
{
- SoupHstsPolicy *copy = g_slice_new0 (SoupHstsPolicy);
+ SoupHSTSPolicy *copy = g_slice_new0 (SoupHSTSPolicy);
copy->domain = g_strdup (policy->domain);
- copy->expires = policy->expires ? soup_date_copy(policy->expires)
- : NULL;
- copy->include_sub_domains = policy->include_sub_domains;
+ copy->max_age = policy->max_age;
+ copy->expires = policy->expires ?
+ soup_date_copy (policy->expires) : NULL;
+ copy->include_subdomains = policy->include_subdomains;
return copy;
}
/**
* soup_hsts_policy_equal:
- * @policy1: a #SoupCookie
- * @policy2: a #SoupCookie
+ * @policy1: a #SoupHSTSPolicy
+ * @policy2: a #SoupHSTSPolicy
*
* Tests if @policy1 and @policy2 are equal.
*
- * Note that currently, this does not check that the cookie domains
- * match. This may change in the future.
- *
- * Return value: whether the cookies are equal.
+ * Returns: whether the policies are equal.
*
* Since: 2.24
*/
gboolean
-soup_hsts_policy_equal (SoupHstsPolicy *policy1, SoupHstsPolicy *policy2)
+soup_hsts_policy_equal (SoupHSTSPolicy *policy1, SoupHSTSPolicy *policy2)
{
g_return_val_if_fail (policy1, FALSE);
g_return_val_if_fail (policy2, FALSE);
@@ -97,7 +101,10 @@ soup_hsts_policy_equal (SoupHstsPolicy *policy1, SoupHstsPolicy *policy2)
if (strcmp (policy1->domain, policy2->domain))
return FALSE;
- if (policy1->include_sub_domains != policy2->include_sub_domains)
+ if (policy1->include_subdomains != policy2->include_subdomains)
+ return FALSE;
+
+ if (policy1->max_age != policy2->max_age)
return FALSE;
if ((policy1->expires && !policy2->expires) ||
@@ -112,186 +119,24 @@ soup_hsts_policy_equal (SoupHstsPolicy *policy1, SoupHstsPolicy *policy2)
return TRUE;
}
-static inline const char *
-skip_lws (const char *s)
-{
- while (g_ascii_isspace (*s))
- s++;
- return s;
-}
-
-static inline const char *
-unskip_lws (const char *s, const char *start)
-{
- while (s > start && g_ascii_isspace (*(s - 1)))
- s--;
- return s;
-}
-
-#define is_attr_ender(ch) ((ch) < ' ' || (ch) == ';' || (ch) == ',' || (ch) == '=')
-#define is_value_ender(ch) ((ch) < ' ' || (ch) == ';')
-
-static char *
-parse_value (const char **val_p, gboolean copy)
-{
- const char *start, *end, *p;
- char *value;
-
- p = *val_p;
- if (*p == '=')
- p++;
- start = skip_lws (p);
- for (p = start; !is_value_ender (*p); p++)
- ;
- end = unskip_lws (p, start);
-
- if (copy)
- value = g_strndup (start, end - start);
- else
- value = NULL;
-
- *val_p = p;
- return value;
-}
-
-static SoupHstsPolicy *
-parse_one_policy (const char *header, SoupURI *origin)
-{
- const char *start, *end, *p;
- gboolean has_value;
- long max_age = -1;
- gboolean include_sub_domains = FALSE;
-
- g_return_val_if_fail (origin == NULL || origin->host, NULL);
-
- p = start = skip_lws (header);
-
- /* Parse directives */
- do {
- if (*p == ';')
- p++;
-
- start = skip_lws (p);
- for (p = start; !is_attr_ender (*p); p++)
- ;
- end = unskip_lws (p, start);
-
- has_value = (*p == '=');
-#define MATCH_NAME(name) ((end - start == strlen (name)) && !g_ascii_strncasecmp (start, name, end - start))
-
- if (MATCH_NAME ("max-age") && has_value) {
- char *max_age_str, *max_age_end;
-
- /* Repeated directives make the policy invalid. */
- if (max_age >= 0)
- goto fail;
-
- max_age_str = parse_value (&p, TRUE);
- max_age = strtol (max_age_str, &max_age_end, 10);
- g_free (max_age_str);
-
- if (*max_age_end == '\0') {
- /* Invalid 'max-age' directive makes the policy invalid. */
- if (max_age < 0)
- goto fail;
- }
- } else if (MATCH_NAME ("includeSubDomains")) {
- /* Repeated directives make the policy invalid. */
- if (include_sub_domains)
- goto fail;
-
- /* The 'includeSubDomains' directive can't have a value. */
- if (has_value)
- goto fail;
-
- include_sub_domains = TRUE;
- } else {
- /* Unknown directives must be skipped. */
- if (has_value)
- parse_value (&p, FALSE);
- }
- } while (*p == ';');
-
- /* No 'max-age' directive makes the policy invalid. */
- if (max_age < 0)
- goto fail;
-
- return soup_hsts_policy_new_with_max_age (origin->host, max_age,
- include_sub_domains);
-
-fail:
- return NULL;
-}
-
-/**
- * Return value: %TRUE if the hostname is suitable for an HSTS host, %FALSE
- * otherwise.
- **/
+/*
+ * Returns: %TRUE if the hostname is suitable for an HSTS host, %FALSE
+ * otherwise. Suitable hostnames are any that is not an IP address.
+ */
static gboolean
is_hostname_valid (const char *hostname)
{
- if (!hostname)
- return FALSE;
-
- /* Hostnames must have at least one '.'
- */
- if (!strchr (hostname, '.'))
- return FALSE;
-
- /* IP addresses are not valid hostnames, only domain names are.
- */
- if (g_hostname_is_ip_address (hostname))
- return FALSE;
-
- /* The hostname should be a valid domain name.
- */
- return TRUE;
+ /* IP addresses are not valid hostnames, only domain names are. */
+ return hostname && !g_hostname_is_ip_address (hostname);
}
/**
* soup_hsts_policy_new:
* @domain: policy domain or hostname
- * @expires: (transfer full): the expiry date of the policy
- * @include_sub_domains: %TRUE if the policy applies on sub domains
- *
- * Creates a new #SoupHstsPolicy with the given attributes.
- *
- * @domain is a domain on which the strict transport security policy
- * represented by this object must be enforced.
- *
- * @expires is the date and time when the policy should be considered
- * expired.
- *
- * If @include_sub_domains is %TRUE, the strict transport security policy
- * must also be enforced on all subdomains of @domain.
- *
- * Return value: a new #SoupHstsPolicy.
- *
- * Since: 2.54
- **/
-SoupHstsPolicy *
-soup_hsts_policy_new (const char *domain, SoupDate *expires,
- gboolean include_sub_domains)
-{
- SoupHstsPolicy *policy;
-
- g_return_val_if_fail (is_hostname_valid (domain), NULL);
-
- policy = g_slice_new0 (SoupHstsPolicy);
- policy->domain = g_strdup (domain);
- policy->expires = expires;
- policy->include_sub_domains = include_sub_domains;
-
- return policy;
-}
-
-/**
- * soup_hsts_policy_new_with_max_age:
- * @domain: policy domain or hostname
* @max_age: max age of the policy
- * @include_sub_domains: %TRUE if the policy applies on sub domains
+ * @include_subdomains: %TRUE if the policy applies on subdomains
*
- * Creates a new #SoupHstsPolicy with the given attributes.
+ * Creates a new #SoupHSTSPolicy with the given attributes.
*
* @domain is a domain on which the strict transport security policy
* represented by this object must be enforced.
@@ -300,22 +145,21 @@ soup_hsts_policy_new (const char *domain, SoupDate *expires,
* SOUP_HSTS_POLICY_MAX_AGE_PAST for an already-expired policy, or a
* lifetime in seconds.
*
- * If @include_sub_domains is %TRUE, the strict transport security policy
+ * If @include_subdomains is %TRUE, the strict transport security policy
* must also be enforced on all subdomains of @domain.
*
- * Return value: a new #SoupHstsPolicy.
+ * Returns: a new #SoupHSTSPolicy.
*
- * Since: 2.54
+ * Since: 2.64
**/
-SoupHstsPolicy *
-soup_hsts_policy_new_with_max_age (const char *domain, int max_age,
- gboolean include_sub_domains)
+SoupHSTSPolicy *
+soup_hsts_policy_new (const char *domain,
+ unsigned long max_age,
+ gboolean include_subdomains)
{
SoupDate *expires;
- SoupHstsPolicy *policy;
g_return_val_if_fail (is_hostname_valid (domain), NULL);
- g_return_val_if_fail (max_age >= 0, NULL);
if (max_age == SOUP_HSTS_POLICY_MAX_AGE_PAST) {
/* Use a date way in the past, to protect against
@@ -325,10 +169,38 @@ soup_hsts_policy_new_with_max_age (const char *domain, int max_age,
} else
expires = soup_date_new_from_now (max_age);
- policy = soup_hsts_policy_new (domain, expires, include_sub_domains);
+ return soup_hsts_policy_new_full (domain, max_age, expires, include_subdomains);
+}
+
+/**
+ * soup_hsts_policy_new_full:
+ * @domain: policy domain or hostname
+ * @max_age: max age of the policy
+ * @expires: the date of expiration of the policy or %NULL for a permanent policy
+ * @include_subdomains: %TRUE if the policy applies on subdomains
+ *
+ * Full version of #soup_hsts_policy_new(), to use with an existing
+ * expiration date. See #soup_hsts_policy_new() for details.
+ *
+ * Returns: a new #SoupHSTSPolicy.
+ *
+ * Since: 2.64
+ **/
+SoupHSTSPolicy *
+soup_hsts_policy_new_full (const char *domain,
+ unsigned long max_age,
+ SoupDate *expires,
+ gboolean include_subdomains)
+{
+ SoupHSTSPolicy *policy;
+
+ g_return_val_if_fail (is_hostname_valid (domain), NULL);
- if (!policy)
- soup_date_free (expires);
+ policy = g_slice_new0 (SoupHSTSPolicy);
+ policy->domain = g_strdup (domain);
+ policy->max_age = max_age;
+ policy->expires = expires;
+ policy->include_subdomains = include_subdomains;
return policy;
}
@@ -336,55 +208,93 @@ soup_hsts_policy_new_with_max_age (const char *domain, int max_age,
/**
* soup_hsts_policy_new_permanent:
* @domain: policy domain or hostname
- * @include_sub_domains: %TRUE if the policy applies on sub domains
+ * @include_subdomains: %TRUE if the policy applies on sub domains
*
- * Creates a new #SoupHstsPolicy with the given attributes.
+ * Creates a new #SoupHSTSPolicy with the given attributes.
*
* @domain is a domain on which the strict transport security policy
* represented by this object must be enforced.
*
- * If @include_sub_domains is %TRUE, the strict transport security policy
+ * If @include_subdomains is %TRUE, the strict transport security policy
* must also be enforced on all subdomains of @domain.
*
- * Return value: a new #SoupHstsPolicy.
+ * Returns: a new #SoupHSTSPolicy.
*
- * Since: 2.54
+ * Since: 2.64
**/
-SoupHstsPolicy *
+SoupHSTSPolicy *
soup_hsts_policy_new_permanent (const char *domain,
- gboolean include_sub_domains)
+ gboolean include_subdomains)
{
- return soup_hsts_policy_new (domain, NULL, include_sub_domains);
+ SoupHSTSPolicy *policy;
+
+ g_return_val_if_fail (is_hostname_valid (domain), NULL);
+
+ policy = soup_hsts_policy_new (domain, 0, include_subdomains);
+ g_clear_pointer (&policy->expires, soup_date_free);
+
+ return policy;
}
/**
* soup_hsts_policy_new_from_response:
- * @msg: a #SoupMessage containing a "Strict-Transport-Security" response
- * header
+ * @msg: a #SoupMessage
*
* Parses @msg's first "Strict-Transport-Security" response header and
- * returns a #SoupHstsPolicy, or %NULL if no valid
- * "Strict-Transport-Security" response header was found.
+ * returns a #SoupHSTSPolicy.
*
- * Return value: (nullable): a new #SoupHstsPolicy, or %NULL if no valid
+ * Returns: (nullable): a new #SoupHSTSPolicy, or %NULL if no valid
* "Strict-Transport-Security" response header was found.
*
- * Since: 2.54
+ * Since: 2.64
**/
-SoupHstsPolicy *
+SoupHSTSPolicy *
soup_hsts_policy_new_from_response (SoupMessage *msg)
{
- SoupURI *origin;
- const char *name, *value;
SoupMessageHeadersIter iter;
+ const char *name, *value;
+
+ g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
soup_message_headers_iter_init (&iter, msg->response_headers);
while (soup_message_headers_iter_next (&iter, &name, &value)) {
- if (g_ascii_strcasecmp (name, "Strict-Transport-Security") != 0)
+ SoupURI *origin;
+ GHashTable *params;
+ const char *max_age_str;
+ char *endptr;
+ unsigned long max_age;
+ gboolean include_subdomains;
+ gpointer include_subdomains_value = NULL;
+ SoupHSTSPolicy *policy = NULL;
+
+ if (strcmp (name, "Strict-Transport-Security") != 0)
continue;
origin = soup_message_get_uri (msg);
- return parse_one_policy (value, origin);
+
+ params = soup_header_parse_semi_param_list (value);
+
+ max_age_str = g_hash_table_lookup (params, "max-age");
+
+ if (!max_age_str)
+ goto out;
+ max_age = strtoul (max_age_str, &endptr, 10);
+ if (*endptr != '\0')
+ goto out;
+
+ include_subdomains = g_hash_table_lookup_extended (params, "includeSubDomains", NULL,
+ &include_subdomains_value);
+ /* includeSubdomains shouldn't have a value. */
+ if (include_subdomains_value)
+ goto out;
+ /* if there are extra params, the HSTS spec demands the header to be ignored. */
+ if (g_hash_table_size (params) > (include_subdomains ? 2 : 1))
+ goto out;
+
+ policy = soup_hsts_policy_new (origin->host, max_age, include_subdomains);
+ out:
+ soup_header_free_param_list (params);
+ return policy;
}
return NULL;
@@ -392,88 +302,94 @@ soup_hsts_policy_new_from_response (SoupMessage *msg)
/**
* soup_hsts_policy_get_domain:
- * @policy: a #SoupHstsPolicy
+ * @policy: a #SoupHSTSPolicy
*
* Gets @policy's domain.
*
- * Return value: @policy's domain.
+ * Returns: (transfer none): @policy's domain.
*
- * Since: 2.54
+ * Since: 2.64
**/
const char *
-soup_hsts_policy_get_domain (SoupHstsPolicy *policy)
+soup_hsts_policy_get_domain (SoupHSTSPolicy *policy)
{
+ g_return_val_if_fail (policy != NULL, NULL);
+
return policy->domain;
}
/**
* soup_hsts_policy_is_expired:
- * @policy: a #SoupHstsPolicy
+ * @policy: a #SoupHSTSPolicy
*
- * Gets whether @policy is expired.
+ * Gets whether @policy is expired. Permanent policies never
+ * expire.
*
- * Permanent policies never expire.
+ * Returns: %TRUE if @policy is expired, %FALSE otherwise.
*
- * Return value: whether @policy is expired.
- *
- * Since: 2.54
+ * Since: 2.64
**/
gboolean
-soup_hsts_policy_is_expired (SoupHstsPolicy *policy)
+soup_hsts_policy_is_expired (SoupHSTSPolicy *policy)
{
+ g_return_val_if_fail (policy != NULL, TRUE);
+
return policy->expires && soup_date_is_past (policy->expires);
}
/**
- * soup_hsts_policy_includes_sub_domains:
- * @policy: a #SoupHstsPolicy
+ * soup_hsts_policy_includes_subdomains:
+ * @policy: a #SoupHSTSPolicy
*
- * Gets whether @policy include its sub-domains.
+ * Gets whether @policy include its subdomains.
*
- * Return value: whether @policy include its sub-domains.
+ * Returns: %TRUE if @policy includes subdomains, %FALSE otherwise.
*
- * Since: 2.54
+ * Since: 2.64
**/
gboolean
-soup_hsts_policy_includes_sub_domains (SoupHstsPolicy *policy)
+soup_hsts_policy_includes_subdomains (SoupHSTSPolicy *policy)
{
- return policy->include_sub_domains;
+ g_return_val_if_fail (policy != NULL, FALSE);
+
+ return policy->include_subdomains;
}
/**
* soup_hsts_policy_is_permanent:
- * @policy: a #SoupHstsPolicy
+ * @policy: a #SoupHSTSPolicy
*
* Gets whether @policy is permanent (not expirable).
*
* A permanent policy never expires and should not be saved by a persistent
- * #SoupHstsEnforcer so the user agent can control them.
+ * #SoupHSTSEnforcer so that the user agent can control them.
*
- * Return value: whether @policy is permanent.
+ * Returns: %TRUE if @policy is permanent, %FALSE otherwise
*
- * Since: 2.54
+ * Since: 2.64
**/
gboolean
-soup_hsts_policy_is_permanent (SoupHstsPolicy *policy)
+soup_hsts_policy_is_permanent (SoupHSTSPolicy *policy)
{
+ g_return_val_if_fail (policy != NULL, FALSE);
+
return !policy->expires;
}
/**
* soup_hsts_policy_free:
- * @policy: a #SoupHstsPolicy
+ * @policy: (transfer full): a #SoupHSTSPolicy
*
* Frees @policy.
*
- * Since: 2.54
+ * Since: 2.64
**/
void
-soup_hsts_policy_free (SoupHstsPolicy *policy)
+soup_hsts_policy_free (SoupHSTSPolicy *policy)
{
g_return_if_fail (policy != NULL);
g_free (policy->domain);
g_clear_pointer (&policy->expires, soup_date_free);
-
- g_slice_free (SoupHstsPolicy, policy);
+ g_slice_free (SoupHSTSPolicy, policy);
}