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