• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * soup-hsts-enforcer.c: HTTP Strict Transport Security enforcer session feature
4  *
5  * Copyright (C) 2016, 2017, 2018 Igalia S.L.
6  * Copyright (C) 2017, 2018 Metrological Group B.V.
7  */
8 
9 #ifdef HAVE_CONFIG_H
10 #include <config.h>
11 #endif
12 
13 #include "soup-hsts-enforcer.h"
14 #include "soup.h"
15 
16 /**
17  * SECTION:soup-hsts-enforcer
18  * @short_description: Automatic HTTP Strict Transport Security enforcing
19  * for #SoupSession
20  *
21  * A #SoupHSTSEnforcer stores HSTS policies and enforces them when
22  * required. #SoupHSTSEnforcer implements #SoupSessionFeature, so you
23  * can add an HSTS enforcer to a session with
24  * soup_session_add_feature() or soup_session_add_feature_by_type().
25  *
26  * #SoupHSTSEnforcer keeps track of all the HTTPS destinations that,
27  * when connected to, return the Strict-Transport-Security header with
28  * valid values. #SoupHSTSEnforcer will forget those destinations
29  * upon expiry or when the server requests it.
30  *
31  * When the #SoupSession the #SoupHSTSEnforcer is attached to queues
32  * or restarts a message, the #SoupHSTSEnforcer will rewrite the URI
33  * to HTTPS if the destination is a known HSTS host and is contacted
34  * over an insecure transport protocol (HTTP). Users of
35  * #SoupHSTSEnforcer are advised to listen to changes in
36  * SoupMessage:uri in order to be aware of changes in the message URI.
37  *
38  * Note that #SoupHSTSEnforcer does not support any form of long-term
39  * HSTS policy persistence. See #SoupHSTSDBEnforcer for a persistent
40  * enforcer.
41  *
42  **/
43 
44 static SoupSessionFeatureInterface *soup_hsts_enforcer_default_feature_interface;
45 static void soup_hsts_enforcer_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
46 
47 enum {
48 	CHANGED,
49 	HSTS_ENFORCED,
50 	LAST_SIGNAL
51 };
52 
53 static guint signals[LAST_SIGNAL] = { 0 };
54 
55 struct _SoupHSTSEnforcerPrivate {
56 	SoupSession *session;
57 	GHashTable *host_policies;
58 	GHashTable *session_policies;
59 };
60 
G_DEFINE_TYPE_WITH_CODE(SoupHSTSEnforcer,soup_hsts_enforcer,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,soup_hsts_enforcer_session_feature_init)G_ADD_PRIVATE (SoupHSTSEnforcer))61 G_DEFINE_TYPE_WITH_CODE (SoupHSTSEnforcer, soup_hsts_enforcer, G_TYPE_OBJECT,
62 			 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
63 						soup_hsts_enforcer_session_feature_init)
64 			 G_ADD_PRIVATE(SoupHSTSEnforcer))
65 
66 static void
67 soup_hsts_enforcer_init (SoupHSTSEnforcer *hsts_enforcer)
68 {
69 	hsts_enforcer->priv = soup_hsts_enforcer_get_instance_private (hsts_enforcer);
70 
71 	hsts_enforcer->priv->host_policies = g_hash_table_new_full (soup_str_case_hash,
72 								    soup_str_case_equal,
73 								    g_free, NULL);
74 
75 	hsts_enforcer->priv->session_policies = g_hash_table_new_full (soup_str_case_hash,
76 								       soup_str_case_equal,
77 								       g_free, NULL);
78 }
79 
80 static void
soup_hsts_enforcer_finalize(GObject * object)81 soup_hsts_enforcer_finalize (GObject *object)
82 {
83 	SoupHSTSEnforcerPrivate *priv = SOUP_HSTS_ENFORCER (object)->priv;
84 	GHashTableIter iter;
85 	gpointer key, value;
86 
87 	g_hash_table_iter_init (&iter, priv->host_policies);
88 	while (g_hash_table_iter_next (&iter, &key, &value))
89 		soup_hsts_policy_free (value);
90 	g_hash_table_destroy (priv->host_policies);
91 
92 	g_hash_table_iter_init (&iter, priv->session_policies);
93 	while (g_hash_table_iter_next (&iter, &key, &value))
94 		soup_hsts_policy_free (value);
95 	g_hash_table_destroy (priv->session_policies);
96 
97 	G_OBJECT_CLASS (soup_hsts_enforcer_parent_class)->finalize (object);
98 }
99 
100 static gboolean
soup_hsts_enforcer_real_is_persistent(SoupHSTSEnforcer * hsts_enforcer)101 soup_hsts_enforcer_real_is_persistent (SoupHSTSEnforcer *hsts_enforcer)
102 {
103 	return FALSE;
104 }
105 
106 static SoupHSTSPolicy *
soup_hsts_enforcer_get_host_policy(SoupHSTSEnforcer * hsts_enforcer,const char * domain)107 soup_hsts_enforcer_get_host_policy (SoupHSTSEnforcer *hsts_enforcer,
108 				    const char *domain)
109 {
110 	return g_hash_table_lookup (hsts_enforcer->priv->host_policies, domain);
111 }
112 
113 static SoupHSTSPolicy *
soup_hsts_enforcer_get_session_policy(SoupHSTSEnforcer * hsts_enforcer,const char * domain)114 soup_hsts_enforcer_get_session_policy (SoupHSTSEnforcer *hsts_enforcer,
115 				       const char *domain)
116 {
117 	return g_hash_table_lookup (hsts_enforcer->priv->session_policies, domain);
118 }
119 
120 static gboolean
soup_hsts_enforcer_real_has_valid_policy(SoupHSTSEnforcer * hsts_enforcer,const char * domain)121 soup_hsts_enforcer_real_has_valid_policy (SoupHSTSEnforcer *hsts_enforcer,
122 					  const char *domain)
123 {
124 	SoupHSTSPolicy *policy;
125 
126 	if (soup_hsts_enforcer_get_session_policy (hsts_enforcer, domain))
127 		return TRUE;
128 
129 	policy = soup_hsts_enforcer_get_host_policy (hsts_enforcer, domain);
130 	if (policy)
131 		return !soup_hsts_policy_is_expired (policy);
132 
133 	return FALSE;
134 }
135 
136 static void
soup_hsts_enforcer_class_init(SoupHSTSEnforcerClass * hsts_enforcer_class)137 soup_hsts_enforcer_class_init (SoupHSTSEnforcerClass *hsts_enforcer_class)
138 {
139 	GObjectClass *object_class = G_OBJECT_CLASS (hsts_enforcer_class);
140 
141 	object_class->finalize = soup_hsts_enforcer_finalize;
142 
143 	hsts_enforcer_class->is_persistent = soup_hsts_enforcer_real_is_persistent;
144 	hsts_enforcer_class->has_valid_policy = soup_hsts_enforcer_real_has_valid_policy;
145 
146 	/**
147 	 * SoupHSTSEnforcer::changed:
148 	 * @hsts_enforcer: the #SoupHSTSEnforcer
149 	 * @old_policy: the old #SoupHSTSPolicy value
150 	 * @new_policy: the new #SoupHSTSPolicy value
151 	 *
152 	 * Emitted when @hsts_enforcer changes. If a policy has been added,
153 	 * @new_policy will contain the newly-added policy and
154 	 * @old_policy will be %NULL. If a policy has been deleted,
155 	 * @old_policy will contain the to-be-deleted policy and
156 	 * @new_policy will be %NULL. If a policy has been changed,
157 	 * @old_policy will contain its old value, and @new_policy its
158 	 * new value.
159 	 *
160 	 * Note that you shouldn't modify the policies from a callback to
161 	 * this signal.
162 	 **/
163 	signals[CHANGED] =
164 		g_signal_new ("changed",
165 			      G_OBJECT_CLASS_TYPE (object_class),
166 			      G_SIGNAL_RUN_FIRST,
167 			      G_STRUCT_OFFSET (SoupHSTSEnforcerClass, changed),
168 			      NULL, NULL,
169 			      NULL,
170 			      G_TYPE_NONE, 2,
171 			      SOUP_TYPE_HSTS_POLICY | G_SIGNAL_TYPE_STATIC_SCOPE,
172 			      SOUP_TYPE_HSTS_POLICY | G_SIGNAL_TYPE_STATIC_SCOPE);
173 
174 	/**
175 	 * SoupHSTSEnforcer::hsts-enforced:
176 	 * @hsts_enforcer: the #SoupHSTSEnforcer
177 	 * @message: the message for which HSTS is being enforced
178 	 *
179 	 * Emitted when @hsts_enforcer has upgraded the protocol
180 	 * for @message to HTTPS as a result of matching its domain with
181 	 * a HSTS policy.
182 	 **/
183 	signals[HSTS_ENFORCED] =
184 		g_signal_new ("hsts-enforced",
185 			      G_OBJECT_CLASS_TYPE (object_class),
186 			      G_SIGNAL_RUN_FIRST,
187 			      G_STRUCT_OFFSET (SoupHSTSEnforcerClass, hsts_enforced),
188 			      NULL, NULL,
189 			      NULL,
190 			      G_TYPE_NONE, 1,
191 			      SOUP_TYPE_MESSAGE);
192 }
193 
194 /**
195  * soup_hsts_enforcer_new:
196  *
197  * Creates a new #SoupHSTSEnforcer. The base #SoupHSTSEnforcer class
198  * does not support persistent storage of HSTS policies, see
199  * #SoupHSTSEnforcerDB for that.
200  *
201  * Returns: a new #SoupHSTSEnforcer
202  *
203  * Since: 2.68
204  **/
205 SoupHSTSEnforcer *
soup_hsts_enforcer_new(void)206 soup_hsts_enforcer_new (void)
207 {
208 	return g_object_new (SOUP_TYPE_HSTS_ENFORCER, NULL);
209 }
210 
211 static void
soup_hsts_enforcer_changed(SoupHSTSEnforcer * hsts_enforcer,SoupHSTSPolicy * old,SoupHSTSPolicy * new)212 soup_hsts_enforcer_changed (SoupHSTSEnforcer *hsts_enforcer,
213 			    SoupHSTSPolicy *old, SoupHSTSPolicy *new)
214 {
215 	g_assert (old || new);
216 
217 	g_signal_emit (hsts_enforcer, signals[CHANGED], 0, old, new);
218 }
219 
220 static gboolean
should_remove_expired_host_policy(G_GNUC_UNUSED gpointer key,SoupHSTSPolicy * policy,SoupHSTSEnforcer * enforcer)221 should_remove_expired_host_policy (G_GNUC_UNUSED gpointer key,
222 				   SoupHSTSPolicy *policy,
223 				   SoupHSTSEnforcer *enforcer)
224 {
225 	if (soup_hsts_policy_is_expired (policy)) {
226 		/* This will emit the ::changed signal before the
227 		   policy is actually removed from the policies hash
228 		   table, which could be problematic, or not.
229 		*/
230 		soup_hsts_enforcer_changed (enforcer, policy, NULL);
231 		soup_hsts_policy_free (policy);
232 
233 		return TRUE;
234 	}
235 
236 	return FALSE;
237 }
238 
239 static void
remove_expired_host_policies(SoupHSTSEnforcer * hsts_enforcer)240 remove_expired_host_policies (SoupHSTSEnforcer *hsts_enforcer)
241 {
242 	g_hash_table_foreach_remove (hsts_enforcer->priv->host_policies,
243 				     (GHRFunc)should_remove_expired_host_policy,
244 				     hsts_enforcer);
245 }
246 
247 static void
soup_hsts_enforcer_remove_host_policy(SoupHSTSEnforcer * hsts_enforcer,const char * domain)248 soup_hsts_enforcer_remove_host_policy (SoupHSTSEnforcer *hsts_enforcer,
249 				       const char *domain)
250 {
251 	SoupHSTSPolicy *policy;
252 
253 	policy = g_hash_table_lookup (hsts_enforcer->priv->host_policies, domain);
254 
255 	if (!policy)
256 		return;
257 
258 	g_hash_table_remove (hsts_enforcer->priv->host_policies, domain);
259 	soup_hsts_enforcer_changed (hsts_enforcer, policy, NULL);
260 	soup_hsts_policy_free (policy);
261 
262 	remove_expired_host_policies (hsts_enforcer);
263 }
264 
265 static void
soup_hsts_enforcer_replace_policy(SoupHSTSEnforcer * hsts_enforcer,SoupHSTSPolicy * new_policy)266 soup_hsts_enforcer_replace_policy (SoupHSTSEnforcer *hsts_enforcer,
267 				   SoupHSTSPolicy *new_policy)
268 {
269 	GHashTable *policies;
270 	SoupHSTSPolicy *old_policy;
271 	const char *domain;
272 	gboolean is_session_policy;
273 
274 	g_assert (!soup_hsts_policy_is_expired (new_policy));
275 
276 	domain = soup_hsts_policy_get_domain (new_policy);
277 	is_session_policy = soup_hsts_policy_is_session_policy (new_policy);
278 
279 	policies = is_session_policy ? hsts_enforcer->priv->session_policies :
280 		                       hsts_enforcer->priv->host_policies;
281 
282 	old_policy = g_hash_table_lookup (policies, domain);
283 	g_assert (old_policy);
284 
285 	g_hash_table_replace (policies, g_strdup (domain), soup_hsts_policy_copy (new_policy));
286 	if (!soup_hsts_policy_equal (old_policy, new_policy))
287 		soup_hsts_enforcer_changed (hsts_enforcer, old_policy, new_policy);
288 	soup_hsts_policy_free (old_policy);
289 
290 	remove_expired_host_policies (hsts_enforcer);
291 }
292 
293 static void
soup_hsts_enforcer_insert_policy(SoupHSTSEnforcer * hsts_enforcer,SoupHSTSPolicy * policy)294 soup_hsts_enforcer_insert_policy (SoupHSTSEnforcer *hsts_enforcer,
295 				  SoupHSTSPolicy *policy)
296 {
297 	GHashTable *policies;
298 	const char *domain;
299 	gboolean is_session_policy;
300 
301 	g_return_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer));
302 	g_return_if_fail (policy != NULL);
303 
304 	g_assert (!soup_hsts_policy_is_expired (policy));
305 
306 	domain = soup_hsts_policy_get_domain (policy);
307 	is_session_policy = soup_hsts_policy_is_session_policy (policy);
308 
309 	g_return_if_fail (domain != NULL);
310 
311 	policies = is_session_policy ? hsts_enforcer->priv->session_policies :
312 				  hsts_enforcer->priv->host_policies;
313 
314 	g_assert (!g_hash_table_contains (policies, domain));
315 
316 	g_hash_table_insert (policies, g_strdup (domain), soup_hsts_policy_copy (policy));
317 	soup_hsts_enforcer_changed (hsts_enforcer, NULL, policy);
318 }
319 
320 /**
321  * soup_hsts_enforcer_set_policy:
322  * @hsts_enforcer: a #SoupHSTSEnforcer
323  * @policy: (transfer none): the policy of the HSTS host
324  *
325  * Sets @policy to @hsts_enforcer. If @policy is expired, any
326  * existing HSTS policy for its host will be removed instead. If a
327  * policy existed for this host, it will be replaced. Otherwise, the
328  * new policy will be inserted. If the policy is a session policy, that
329  * is, one created with soup_hsts_policy_new_session_policy(), the policy
330  * will not expire and will be enforced during the lifetime of
331  * @hsts_enforcer's #SoupSession.
332  *
333  * Since: 2.68
334  **/
335 void
soup_hsts_enforcer_set_policy(SoupHSTSEnforcer * hsts_enforcer,SoupHSTSPolicy * policy)336 soup_hsts_enforcer_set_policy (SoupHSTSEnforcer *hsts_enforcer,
337 			       SoupHSTSPolicy *policy)
338 {
339 	GHashTable *policies;
340 	const char *domain;
341 	gboolean is_session_policy;
342 	SoupHSTSPolicy *current_policy;
343 
344 	g_return_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer));
345 	g_return_if_fail (policy != NULL);
346 
347 	domain = soup_hsts_policy_get_domain (policy);
348 	g_return_if_fail (domain != NULL);
349 
350 	is_session_policy = soup_hsts_policy_is_session_policy (policy);
351 	policies = is_session_policy ? hsts_enforcer->priv->session_policies :
352 				  hsts_enforcer->priv->host_policies;
353 
354 	if (!is_session_policy && soup_hsts_policy_is_expired (policy)) {
355 		soup_hsts_enforcer_remove_host_policy (hsts_enforcer, domain);
356 		return;
357 	}
358 
359 	current_policy = g_hash_table_lookup (policies, domain);
360 
361 	if (current_policy)
362 		soup_hsts_enforcer_replace_policy (hsts_enforcer, policy);
363 	else
364 		soup_hsts_enforcer_insert_policy (hsts_enforcer, policy);
365 }
366 
367 /**
368  * soup_hsts_enforcer_set_session_policy:
369  * @hsts_enforcer: a #SoupHSTSEnforcer
370  * @domain: policy domain or hostname
371  * @include_subdomains: %TRUE if the policy applies on sub domains
372  *
373  * Sets a session policy for @domain. A session policy is a policy
374  * that is permanent to the lifetime of @hsts_enforcer's #SoupSession
375  * and doesn't expire.
376  *
377  * Since: 2.68
378  **/
379 void
soup_hsts_enforcer_set_session_policy(SoupHSTSEnforcer * hsts_enforcer,const char * domain,gboolean include_subdomains)380 soup_hsts_enforcer_set_session_policy (SoupHSTSEnforcer *hsts_enforcer,
381 				       const char *domain,
382 				       gboolean include_subdomains)
383 {
384 	SoupHSTSPolicy *policy;
385 
386 	g_return_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer));
387 	g_return_if_fail (domain != NULL);
388 
389 	policy = soup_hsts_policy_new_session_policy (domain, include_subdomains);
390 	soup_hsts_enforcer_set_policy (hsts_enforcer, policy);
391 	soup_hsts_policy_free (policy);
392 }
393 
394 static gboolean
soup_hsts_enforcer_host_includes_subdomains(SoupHSTSEnforcer * hsts_enforcer,const char * domain)395 soup_hsts_enforcer_host_includes_subdomains (SoupHSTSEnforcer *hsts_enforcer,
396 					     const char *domain)
397 {
398 	SoupHSTSPolicy *policy;
399 	gboolean include_subdomains = FALSE;
400 
401 	g_return_val_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer), FALSE);
402 	g_return_val_if_fail (domain != NULL, FALSE);
403 
404 	policy = soup_hsts_enforcer_get_session_policy (hsts_enforcer, domain);
405 	if (policy)
406 		include_subdomains |= soup_hsts_policy_includes_subdomains (policy);
407 
408 	policy = soup_hsts_enforcer_get_host_policy (hsts_enforcer, domain);
409 	if (policy)
410 		include_subdomains |= soup_hsts_policy_includes_subdomains (policy);
411 
412 	return include_subdomains;
413 }
414 
415 static inline const char *
super_domain_of(const char * domain)416 super_domain_of (const char *domain)
417 {
418 	const char *iter = domain;
419 
420 	g_assert (domain);
421 
422 	for (; *iter != '\0' && *iter != '.' ; iter++);
423 	for (; *iter == '.' ; iter++);
424 
425 	if (*iter == '\0')
426 		return NULL;
427 
428 	return iter;
429 }
430 
431 static gboolean
soup_hsts_enforcer_must_enforce_secure_transport(SoupHSTSEnforcer * hsts_enforcer,const char * domain)432 soup_hsts_enforcer_must_enforce_secure_transport (SoupHSTSEnforcer *hsts_enforcer,
433 						  const char *domain)
434 {
435 	const char *super_domain = domain;
436 
437 	g_return_val_if_fail (domain != NULL, FALSE);
438 
439 	if (soup_hsts_enforcer_has_valid_policy (hsts_enforcer, domain))
440 		return TRUE;
441 
442 	while ((super_domain = super_domain_of (super_domain)) != NULL) {
443 		if (soup_hsts_enforcer_host_includes_subdomains (hsts_enforcer, super_domain) &&
444 		    soup_hsts_enforcer_has_valid_policy (hsts_enforcer, super_domain))
445 			return TRUE;
446 	}
447 
448 	return FALSE;
449 }
450 
451 static void
soup_hsts_enforcer_process_sts_header(SoupHSTSEnforcer * hsts_enforcer,SoupMessage * msg)452 soup_hsts_enforcer_process_sts_header (SoupHSTSEnforcer *hsts_enforcer,
453 				       SoupMessage *msg)
454 {
455 	SoupHSTSPolicy *policy;
456 	SoupURI *uri;
457 
458 	uri = soup_message_get_uri (msg);
459 
460 	g_return_if_fail (uri != NULL);
461 
462 	policy = soup_hsts_policy_new_from_response (msg);
463 	if (policy) {
464 		soup_hsts_enforcer_set_policy (hsts_enforcer, policy);
465 		soup_hsts_policy_free (policy);
466 	}
467 }
468 
469 static void
got_sts_header_cb(SoupMessage * msg,gpointer user_data)470 got_sts_header_cb (SoupMessage *msg, gpointer user_data)
471 {
472 	SoupHSTSEnforcer *hsts_enforcer = SOUP_HSTS_ENFORCER (user_data);
473 
474 	soup_hsts_enforcer_process_sts_header (hsts_enforcer, msg);
475 }
476 
477 static void
rewrite_message_uri_to_https(SoupMessage * msg)478 rewrite_message_uri_to_https (SoupMessage *msg)
479 {
480 	SoupURI *uri;
481 	guint original_port;
482 
483 	uri = soup_uri_copy (soup_message_get_uri (msg));
484 
485 	original_port = soup_uri_get_port (uri);
486 	/* This will unconditionally rewrite the port to 443. */
487 	soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTPS);
488 	/* From the RFC: "If the URI contains an explicit port component that
489 	   is not equal to "80", the port component value MUST be preserved;" */
490 	if (original_port != 80)
491 		soup_uri_set_port (uri, original_port);
492 
493 	soup_message_set_uri (msg, uri);
494 	soup_uri_free (uri);
495 }
496 
497 static void
on_sts_known_host_message_starting(SoupMessage * msg,SoupHSTSEnforcer * enforcer)498 on_sts_known_host_message_starting (SoupMessage *msg, SoupHSTSEnforcer *enforcer)
499 {
500 	GTlsCertificateFlags errors;
501 
502 	/* THE UA MUST terminate the connection if there are
503 	   any errors with the underlying secure transport for STS
504 	   known hosts. */
505 
506 	soup_message_get_https_status (msg, NULL, &errors);
507 	if (errors)
508 		soup_session_cancel_message (enforcer->priv->session, msg, SOUP_STATUS_CANCELLED);
509 }
510 
511 static void
preprocess_request(SoupHSTSEnforcer * enforcer,SoupMessage * msg)512 preprocess_request (SoupHSTSEnforcer *enforcer, SoupMessage *msg)
513 {
514 	SoupURI *uri;
515 	const char *scheme;
516 	const char *host;
517 	char *canonicalized = NULL;
518 
519 	uri = soup_message_get_uri (msg);
520 	host = soup_uri_get_host (uri);
521 
522 	if (g_hostname_is_ip_address (host))
523 		return;
524 
525 	scheme = soup_uri_get_scheme (uri);
526 	if (scheme == SOUP_URI_SCHEME_HTTP) {
527 		if (g_hostname_is_ascii_encoded (host)) {
528 			canonicalized = g_hostname_to_unicode (host);
529 			if (!canonicalized)
530 				return;
531 		}
532 		if (soup_hsts_enforcer_must_enforce_secure_transport (enforcer, canonicalized? canonicalized : host)) {
533 			rewrite_message_uri_to_https (msg);
534 			g_signal_connect (msg, "starting",
535 					  G_CALLBACK (on_sts_known_host_message_starting),
536 					  enforcer);
537 			g_signal_emit (enforcer, signals[HSTS_ENFORCED], 0, msg);
538 		}
539 		g_free (canonicalized);
540 	} else if (scheme == SOUP_URI_SCHEME_HTTPS) {
541 		soup_message_add_header_handler (msg, "got-headers",
542 						 "Strict-Transport-Security",
543 						 G_CALLBACK (got_sts_header_cb),
544 						 enforcer);
545 	}
546 }
547 
548 static void
message_restarted_cb(SoupMessage * msg,gpointer user_data)549 message_restarted_cb (SoupMessage *msg, gpointer user_data)
550 {
551 	preprocess_request (SOUP_HSTS_ENFORCER (user_data), msg);
552 
553 }
554 
555 static void
soup_hsts_enforcer_attach(SoupSessionFeature * feature,SoupSession * session)556 soup_hsts_enforcer_attach (SoupSessionFeature *feature, SoupSession *session)
557 {
558 	SOUP_HSTS_ENFORCER (feature)->priv->session = session;
559 
560 	if (soup_hsts_enforcer_default_feature_interface->attach)
561 		soup_hsts_enforcer_default_feature_interface->attach (feature, session);
562 }
563 
564 static void
soup_hsts_enforcer_request_queued(SoupSessionFeature * feature,SoupSession * session,SoupMessage * msg)565 soup_hsts_enforcer_request_queued (SoupSessionFeature *feature,
566 				   SoupSession *session,
567 				   SoupMessage *msg)
568 {
569 	g_signal_connect (msg, "restarted", G_CALLBACK (message_restarted_cb), feature);
570 	preprocess_request (SOUP_HSTS_ENFORCER (feature), msg);
571 
572 	if (soup_hsts_enforcer_default_feature_interface->request_queued)
573 		soup_hsts_enforcer_default_feature_interface->request_queued (feature, session, msg);
574 }
575 
576 static void
soup_hsts_enforcer_request_unqueued(SoupSessionFeature * feature,SoupSession * session,SoupMessage * msg)577 soup_hsts_enforcer_request_unqueued (SoupSessionFeature *feature,
578 				     SoupSession *session,
579 				     SoupMessage *msg)
580 {
581 	g_signal_handlers_disconnect_by_func (msg, message_restarted_cb, feature);
582 	g_signal_handlers_disconnect_by_func (msg, got_sts_header_cb, feature);
583 
584 	if (soup_hsts_enforcer_default_feature_interface->request_unqueued)
585 		soup_hsts_enforcer_default_feature_interface->request_unqueued (feature, session, msg);
586 }
587 
588 static void
soup_hsts_enforcer_session_feature_init(SoupSessionFeatureInterface * feature_interface,gpointer interface_data)589 soup_hsts_enforcer_session_feature_init (SoupSessionFeatureInterface *feature_interface,
590 					 gpointer interface_data)
591 {
592 	soup_hsts_enforcer_default_feature_interface =
593 		g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
594 
595 	feature_interface->attach = soup_hsts_enforcer_attach;
596 	feature_interface->request_queued = soup_hsts_enforcer_request_queued;
597 	feature_interface->request_unqueued = soup_hsts_enforcer_request_unqueued;
598 }
599 
600 /**
601  * soup_hsts_enforcer_is_persistent:
602  * @hsts_enforcer: a #SoupHSTSEnforcer
603  *
604  * Gets whether @hsts_enforcer stores policies persistenly.
605  *
606  * Returns: %TRUE if @hsts_enforcer storage is persistent or %FALSE otherwise.
607  *
608  * Since: 2.68
609  **/
610 gboolean
soup_hsts_enforcer_is_persistent(SoupHSTSEnforcer * hsts_enforcer)611 soup_hsts_enforcer_is_persistent (SoupHSTSEnforcer *hsts_enforcer)
612 {
613 	g_return_val_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer), FALSE);
614 
615 	return SOUP_HSTS_ENFORCER_GET_CLASS (hsts_enforcer)->is_persistent (hsts_enforcer);
616 }
617 
618 /**
619  * soup_hsts_enforcer_has_valid_policy:
620  * @hsts_enforcer: a #SoupHSTSEnforcer
621  * @domain: a domain.
622  *
623  * Gets whether @hsts_enforcer has a currently valid policy for @domain.
624  *
625  * Returns: %TRUE if access to @domain should happen over HTTPS, false
626  * otherwise.
627  *
628  * Since: 2.68
629  **/
630 gboolean
soup_hsts_enforcer_has_valid_policy(SoupHSTSEnforcer * hsts_enforcer,const char * domain)631 soup_hsts_enforcer_has_valid_policy (SoupHSTSEnforcer *hsts_enforcer,
632 				     const char *domain)
633 {
634 	char *canonicalized = NULL;
635 	gboolean retval;
636 
637 	g_return_val_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer), FALSE);
638 	g_return_val_if_fail (domain != NULL, FALSE);
639 
640 	if (g_hostname_is_ascii_encoded (domain)) {
641 		canonicalized = g_hostname_to_unicode (domain);
642 		g_return_val_if_fail (canonicalized, FALSE);
643 	}
644 
645 	retval = SOUP_HSTS_ENFORCER_GET_CLASS (hsts_enforcer)->has_valid_policy (hsts_enforcer,
646 										 canonicalized ? canonicalized : domain);
647 
648 	g_free (canonicalized);
649 
650 	return retval;
651 }
652 
653 static void
add_domain_to_list(gpointer key,gpointer value,gpointer data)654 add_domain_to_list (gpointer key,
655 		    gpointer value,
656 		    gpointer data)
657 {
658 	GList **domains = (GList **) data;
659 	*domains = g_list_prepend (*domains, g_strdup ((char*)key));
660 }
661 
662 /**
663  * soup_hsts_enforcer_get_domains:
664  * @hsts_enforcer: a #SoupHSTSEnforcer
665  * @session_policies: whether to include session policies
666  *
667  * Gets a list of domains for which there are policies in @enforcer.
668  *
669  * Since: 2.68
670  *
671  * Returns: (element-type utf8) (transfer full): a newly allocated
672  * list of domains. Use g_list_free_full() and g_free() to free the
673  * list.
674  **/
675 GList*
soup_hsts_enforcer_get_domains(SoupHSTSEnforcer * hsts_enforcer,gboolean session_policies)676 soup_hsts_enforcer_get_domains (SoupHSTSEnforcer *hsts_enforcer,
677 				gboolean          session_policies)
678 {
679 	GList *domains = NULL;
680 
681 	g_return_val_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer), NULL);
682 
683 	g_hash_table_foreach (hsts_enforcer->priv->host_policies, add_domain_to_list, &domains);
684 	if (session_policies)
685 		g_hash_table_foreach (hsts_enforcer->priv->session_policies, add_domain_to_list, &domains);
686 
687 	return domains;
688 }
689 
690 static void
add_policy_to_list(gpointer key,gpointer value,gpointer data)691 add_policy_to_list (gpointer key,
692 		    gpointer value,
693 		    gpointer data)
694 {
695 	GList **policies = (GList **) data;
696 	*policies = g_list_prepend (*policies, soup_hsts_policy_copy ((SoupHSTSPolicy*)value));
697 }
698 
699 /**
700  * soup_hsts_enforcer_get_policies:
701  * @hsts_enforcer: a #SoupHSTSEnforcer
702  * @session_policies: whether to include session policies
703  *
704  * Gets a list with the policies in @enforcer.
705  *
706  * Returns: (element-type SoupHSTSPolicy) (transfer full): a newly
707  * allocated list of policies. Use g_list_free_full() and
708  * soup_hsts_policy_free() to free the list.
709  *
710  * Since: 2.68
711  *
712  **/
713 GList*
soup_hsts_enforcer_get_policies(SoupHSTSEnforcer * hsts_enforcer,gboolean session_policies)714 soup_hsts_enforcer_get_policies (SoupHSTSEnforcer *hsts_enforcer,
715 				 gboolean          session_policies)
716 {
717 	GList *policies = NULL;
718 
719 	g_return_val_if_fail (SOUP_IS_HSTS_ENFORCER (hsts_enforcer), NULL);
720 
721 	g_hash_table_foreach (hsts_enforcer->priv->host_policies, add_policy_to_list, &policies);
722 	if (session_policies)
723 		g_hash_table_foreach (hsts_enforcer->priv->session_policies, add_policy_to_list, &policies);
724 
725 	return policies;
726 }
727