1 /* HTTP Strict Transport Security (HSTS) support.
2 Copyright (C) 1996-2012, 2015, 2018-2021 Free Software Foundation,
3 Inc.
4
5 This file is part of GNU Wget.
6
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Wget. If not, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work. */
30 #include "wget.h"
31
32 #ifdef HAVE_HSTS
33 #include "hsts.h"
34 #include "utils.h"
35 #include "host.h" /* for is_valid_ip_address() */
36 #include "hash.h"
37 #include "c-ctype.h"
38 #ifdef TESTING
39 #include "init.h" /* for ajoin_dir_file() */
40 #include "../tests/unit-tests.h"
41 #endif
42
43 #include <unistd.h>
44 #include <sys/types.h>
45 #include <stdlib.h>
46 #include <time.h>
47 #include <sys/stat.h>
48 #include <string.h>
49 #include <stdio.h>
50 #include <sys/file.h>
51
52 struct hsts_store {
53 struct hash_table *table;
54 time_t last_mtime;
55 bool changed;
56 };
57
58 struct hsts_kh {
59 char *host;
60 int explicit_port;
61 };
62
63 struct hsts_kh_info {
64 time_t created;
65 time_t max_age;
66 bool include_subdomains;
67 };
68
69 enum hsts_kh_match {
70 NO_MATCH,
71 SUPERDOMAIN_MATCH,
72 CONGRUENT_MATCH
73 };
74
75 #define hsts_is_host_name_valid(host) (!is_valid_ip_address (host))
76 #define hsts_is_scheme_valid(scheme) (scheme == SCHEME_HTTPS)
77 #define hsts_is_host_eligible(scheme, host) \
78 (hsts_is_scheme_valid (scheme) && hsts_is_host_name_valid (host))
79
80 #define DEFAULT_HTTP_PORT 80
81 #define DEFAULT_SSL_PORT 443
82 #define MAKE_EXPLICIT_PORT(s, p) (s == SCHEME_HTTPS ? (p == DEFAULT_SSL_PORT ? 0 : p) \
83 : (p == DEFAULT_HTTP_PORT ? 0 : p))
84
85 /* Hashing and comparison functions for the hash table */
86
87 #ifdef __clang__
88 __attribute__((no_sanitize("integer")))
89 #endif
90 static unsigned long
hsts_hash_func(const void * key)91 hsts_hash_func (const void *key)
92 {
93 struct hsts_kh *k = (struct hsts_kh *) key;
94 const char *h = NULL;
95 unsigned int hash = k->explicit_port;
96
97 for (h = k->host; *h; h++)
98 hash = hash * 31 + *h;
99
100 return hash;
101 }
102
103 static int
hsts_cmp_func(const void * h1,const void * h2)104 hsts_cmp_func (const void *h1, const void *h2)
105 {
106 struct hsts_kh *kh1 = (struct hsts_kh *) h1,
107 *kh2 = (struct hsts_kh *) h2;
108
109 return (!strcmp (kh1->host, kh2->host)) && (kh1->explicit_port == kh2->explicit_port);
110 }
111
112 /* Private functions. Feel free to make some of these public when needed. */
113
114 static struct hsts_kh_info *
hsts_find_entry(hsts_store_t store,const char * host,int explicit_port,enum hsts_kh_match * match_type,struct hsts_kh * kh)115 hsts_find_entry (hsts_store_t store,
116 const char *host, int explicit_port,
117 enum hsts_kh_match *match_type,
118 struct hsts_kh *kh)
119 {
120 struct hsts_kh *k = NULL;
121 struct hsts_kh_info *khi = NULL;
122 enum hsts_kh_match match = NO_MATCH;
123 char *pos = NULL;
124 char *org_ptr = NULL;
125
126 k = (struct hsts_kh *) xnew (struct hsts_kh);
127 k->host = xstrdup_lower (host);
128 k->explicit_port = explicit_port;
129
130 /* save pointer so that we don't get into trouble later when freeing */
131 org_ptr = k->host;
132
133 khi = (struct hsts_kh_info *) hash_table_get (store->table, k);
134 if (khi)
135 {
136 match = CONGRUENT_MATCH;
137 goto end;
138 }
139
140 while (match == NO_MATCH &&
141 (pos = strchr (k->host, '.')) && pos - k->host > 0 &&
142 strchr (pos + 1, '.'))
143 {
144 k->host += (pos - k->host + 1);
145 khi = (struct hsts_kh_info *) hash_table_get (store->table, k);
146 if (khi)
147 match = SUPERDOMAIN_MATCH;
148 }
149
150 end:
151 /* restore pointer or we'll get a SEGV */
152 k->host = org_ptr;
153
154 /* copy parameters to previous frame */
155 if (match_type)
156 *match_type = match;
157 if (kh)
158 memcpy (kh, k, sizeof (struct hsts_kh));
159 else
160 xfree (k->host);
161
162 xfree (k);
163 return khi;
164 }
165
166 static bool
hsts_new_entry_internal(hsts_store_t store,const char * host,int port,time_t created,time_t max_age,bool include_subdomains,bool check_validity,bool check_expired,bool check_duplicates)167 hsts_new_entry_internal (hsts_store_t store,
168 const char *host, int port,
169 time_t created, time_t max_age,
170 bool include_subdomains,
171 bool check_validity,
172 bool check_expired,
173 bool check_duplicates)
174 {
175 struct hsts_kh *kh = xnew (struct hsts_kh);
176 struct hsts_kh_info *khi = xnew0 (struct hsts_kh_info);
177 bool success = false;
178
179 kh->host = xstrdup_lower (host);
180 kh->explicit_port = MAKE_EXPLICIT_PORT (SCHEME_HTTPS, port);
181
182 khi->created = created;
183 khi->max_age = max_age;
184 khi->include_subdomains = include_subdomains;
185
186 /* Check validity */
187 if (check_validity && !hsts_is_host_name_valid (host))
188 goto bail;
189
190 if (check_expired && ((khi->created + khi->max_age) < khi->created))
191 goto bail;
192
193 if (check_duplicates && hash_table_contains (store->table, kh))
194 goto bail;
195
196 /* Now store the new entry */
197 hash_table_put (store->table, kh, khi);
198 success = true;
199
200 bail:
201 if (!success)
202 {
203 /* abort! */
204 xfree (kh->host);
205 xfree (kh);
206 xfree (khi);
207 }
208
209 return success;
210 }
211
212 /*
213 Creates a new entry, but does not check whether that entry already exists.
214 This function assumes that check has already been done by the caller.
215 */
216 static bool
hsts_add_entry(hsts_store_t store,const char * host,int port,time_t max_age,bool include_subdomains)217 hsts_add_entry (hsts_store_t store,
218 const char *host, int port,
219 time_t max_age, bool include_subdomains)
220 {
221 time_t t = time (NULL);
222
223 /* It might happen time() returned -1 */
224 return (t == (time_t)(-1) ?
225 false :
226 hsts_new_entry_internal (store, host, port, t, max_age, include_subdomains, false, true, false));
227 }
228
229 /* Creates a new entry, unless an identical one already exists. */
230 static bool
hsts_new_entry(hsts_store_t store,const char * host,int port,time_t created,time_t max_age,bool include_subdomains)231 hsts_new_entry (hsts_store_t store,
232 const char *host, int port,
233 time_t created, time_t max_age,
234 bool include_subdomains)
235 {
236 return hsts_new_entry_internal (store, host, port, created, max_age, include_subdomains, true, true, true);
237 }
238
239 static void
hsts_remove_entry(hsts_store_t store,struct hsts_kh * kh)240 hsts_remove_entry (hsts_store_t store, struct hsts_kh *kh)
241 {
242 hash_table_remove (store->table, kh);
243 }
244
245 static bool
hsts_store_merge(hsts_store_t store,const char * host,int port,time_t created,time_t max_age,bool include_subdomains)246 hsts_store_merge (hsts_store_t store,
247 const char *host, int port,
248 time_t created, time_t max_age,
249 bool include_subdomains)
250 {
251 enum hsts_kh_match match_type = NO_MATCH;
252 struct hsts_kh_info *khi = NULL;
253 bool success = false;
254
255 port = MAKE_EXPLICIT_PORT (SCHEME_HTTPS, port);
256 khi = hsts_find_entry (store, host, port, &match_type, NULL);
257 if (khi && match_type == CONGRUENT_MATCH && created > khi->created)
258 {
259 /* update the entry with the new info */
260 khi->created = created;
261 khi->max_age = max_age;
262 khi->include_subdomains = include_subdomains;
263
264 success = true;
265 }
266 else if (!khi)
267 success = hsts_new_entry (store, host, port, created, max_age, include_subdomains);
268
269 return success;
270 }
271
272 static bool
hsts_read_database(hsts_store_t store,FILE * fp,bool merge_with_existing_entries)273 hsts_read_database (hsts_store_t store, FILE *fp, bool merge_with_existing_entries)
274 {
275 char *line = NULL, *p;
276 size_t len = 0;
277 int items_read;
278 bool result = false;
279 bool (*func)(hsts_store_t, const char *, int, time_t, time_t, bool);
280
281 char host[256];
282 int port;
283 time_t created, max_age;
284 int include_subdomains;
285
286 func = (merge_with_existing_entries ? hsts_store_merge : hsts_new_entry);
287
288 while (getline (&line, &len, fp) > 0)
289 {
290 for (p = line; c_isspace (*p); p++)
291 ;
292
293 if (*p == '#')
294 continue;
295
296 items_read = sscanf (p, "%255s %d %d %lu %lu",
297 host,
298 &port,
299 &include_subdomains,
300 (unsigned long *) &created,
301 (unsigned long *) &max_age);
302
303 if (items_read == 5)
304 func (store, host, port, created, max_age, !!include_subdomains);
305 }
306
307 xfree (line);
308 result = true;
309
310 return result;
311 }
312
313 static void
hsts_store_dump(hsts_store_t store,FILE * fp)314 hsts_store_dump (hsts_store_t store, FILE *fp)
315 {
316 hash_table_iterator it;
317
318 /* Print preliminary comments. We don't care if any of these fail. */
319 fputs ("# HSTS 1.0 Known Hosts database for GNU Wget.\n", fp);
320 fputs ("# Edit at your own risk.\n", fp);
321 fputs ("# <hostname>\t<port>\t<incl. subdomains>\t<created>\t<max-age>\n", fp);
322
323 /* Now cycle through the HSTS store in memory and dump the entries */
324 for (hash_table_iterate (store->table, &it); hash_table_iter_next (&it);)
325 {
326 struct hsts_kh *kh = (struct hsts_kh *) it.key;
327 struct hsts_kh_info *khi = (struct hsts_kh_info *) it.value;
328
329 if (fprintf (fp, "%s\t%d\t%d\t%lu\t%lu\n",
330 kh->host, kh->explicit_port, khi->include_subdomains,
331 (unsigned long) khi->created,
332 (unsigned long) khi->max_age) < 0)
333 {
334 logprintf (LOG_ALWAYS, "Could not write the HSTS database correctly.\n");
335 break;
336 }
337 }
338 }
339
340 /*
341 * Test:
342 * - The file is a regular file (ie. not a symlink), and
343 * - The file is not world-writable.
344 */
345 static bool
hsts_file_access_valid(const char * filename)346 hsts_file_access_valid (const char *filename)
347 {
348 struct stat st;
349
350 if (stat (filename, &st) == -1)
351 return false;
352
353 return
354 #ifndef WINDOWS
355 /*
356 * The world-writable concept is a Unix-centric notion.
357 * We bypass this test on Windows.
358 */
359 !(st.st_mode & S_IWOTH) &&
360 #endif
361 S_ISREG (st.st_mode);
362 }
363
364 /* HSTS API */
365
366 /*
367 Changes the given URLs according to the HSTS policy.
368
369 If there's no host in the store that either congruently
370 or not, matches the given URL, no changes are made.
371 Returns true if the URL was changed, or false
372 if it was left intact.
373 */
374 bool
hsts_match(hsts_store_t store,struct url * u)375 hsts_match (hsts_store_t store, struct url *u)
376 {
377 bool url_changed = false;
378 struct hsts_kh_info *entry = NULL;
379 struct hsts_kh *kh = xnew(struct hsts_kh);
380 enum hsts_kh_match match = NO_MATCH;
381 int port = MAKE_EXPLICIT_PORT (u->scheme, u->port);
382
383 /* avoid doing any computation if we're already in HTTPS */
384 if (!hsts_is_scheme_valid (u->scheme))
385 {
386 entry = hsts_find_entry (store, u->host, port, &match, kh);
387 if (entry)
388 {
389 if ((entry->created + entry->max_age) >= time(NULL))
390 {
391 if ((match == CONGRUENT_MATCH) ||
392 (match == SUPERDOMAIN_MATCH && entry->include_subdomains))
393 {
394 /* we found a matching Known HSTS Host
395 rewrite the URL */
396 u->scheme = SCHEME_HTTPS;
397 if (u->port == 80)
398 u->port = 443;
399 url_changed = true;
400 store->changed = true;
401 }
402 }
403 else
404 {
405 hsts_remove_entry (store, kh);
406 store->changed = true;
407 }
408 }
409 xfree (kh->host);
410 }
411
412 xfree (kh);
413
414 return url_changed;
415 }
416
417 /*
418 Add a new HSTS Known Host to the HSTS store.
419
420 If the host already exists, its information is updated,
421 or it'll be removed from the store if max_age is zero.
422
423 Bear in mind that the store is kept in memory, and will not
424 be written to disk until hsts_store_save is called.
425 This function regrows the in-memory HSTS store if necessary.
426
427 Currently, for a host to be taken into consideration,
428 two conditions have to be met:
429 - Connection must be through a secure channel (HTTPS).
430 - The host must not be an IPv4 or IPv6 address.
431
432 The RFC 6797 states that hosts that match IPv4 or IPv6 format
433 should be discarded at URI rewrite time. But we short-circuit
434 that check here, since there's no point in storing a host that
435 will never be matched.
436
437 Returns true if a new entry was actually created, or false
438 if an existing entry was updated/deleted. */
439 bool
hsts_store_entry(hsts_store_t store,enum url_scheme scheme,const char * host,int port,time_t max_age,bool include_subdomains)440 hsts_store_entry (hsts_store_t store,
441 enum url_scheme scheme, const char *host, int port,
442 time_t max_age, bool include_subdomains)
443 {
444 bool result = false;
445 enum hsts_kh_match match = NO_MATCH;
446 struct hsts_kh *kh = xnew(struct hsts_kh);
447 struct hsts_kh_info *entry = NULL;
448
449 if (hsts_is_host_eligible (scheme, host))
450 {
451 port = MAKE_EXPLICIT_PORT (scheme, port);
452 entry = hsts_find_entry (store, host, port, &match, kh);
453 if (entry && match == CONGRUENT_MATCH)
454 {
455 if (max_age == 0)
456 {
457 hsts_remove_entry (store, kh);
458 store->changed = true;
459 }
460 else if (max_age > 0)
461 {
462 /* RFC 6797 states that 'max_age' is a TTL relative to the
463 * reception of the STS header so we have to update the
464 * 'created' field too. The RFC also states that we have to
465 * update the entry each time we see HSTS header.
466 * See also Section 11.2. */
467 time_t t = time (NULL);
468
469 if (t != (time_t)(-1) && t != entry->created)
470 {
471 entry->created = t;
472 entry->max_age = max_age;
473 entry->include_subdomains = include_subdomains;
474 store->changed = true;
475 }
476 }
477 /* we ignore negative max_ages */
478 }
479 else if (entry == NULL || match == SUPERDOMAIN_MATCH)
480 {
481 /* Either we didn't find a matching host,
482 or we got a superdomain match.
483 In either case, we create a new entry.
484
485 We have to perform an explicit check because it might
486 happen we got a non-existent entry with max_age == 0.
487 */
488 result = hsts_add_entry (store, host, port, max_age, include_subdomains);
489 if (result)
490 store->changed = true;
491 }
492 /* we ignore new entries with max_age == 0 */
493 xfree (kh->host);
494 }
495
496 xfree (kh);
497
498 return result;
499 }
500
501 hsts_store_t
hsts_store_open(const char * filename)502 hsts_store_open (const char *filename)
503 {
504 hsts_store_t store = NULL;
505 file_stats_t fstats;
506
507 store = xnew0 (struct hsts_store);
508 store->table = hash_table_new (0, hsts_hash_func, hsts_cmp_func);
509 store->last_mtime = 0;
510 store->changed = false;
511
512 if (file_exists_p (filename, &fstats))
513 {
514 if (hsts_file_access_valid (filename))
515 {
516 struct stat st;
517 FILE *fp = fopen_stat (filename, "r", &fstats);
518
519 if (!fp || !hsts_read_database (store, fp, false))
520 {
521 /* abort! */
522 hsts_store_close (store);
523 xfree (store);
524 if (fp)
525 fclose (fp);
526 goto out;
527 }
528
529 if (fstat (fileno (fp), &st) == 0)
530 store->last_mtime = st.st_mtime;
531
532 fclose (fp);
533 }
534 else
535 {
536 /*
537 * If we're not reading the HSTS database,
538 * then by all means act as if HSTS was disabled.
539 */
540 hsts_store_close (store);
541 xfree (store);
542
543 logprintf (LOG_NOTQUIET, "Will not apply HSTS. "
544 "The HSTS database must be a regular and non-world-writable file.\n");
545 }
546 }
547
548 out:
549 return store;
550 }
551
552 void
hsts_store_save(hsts_store_t store,const char * filename)553 hsts_store_save (hsts_store_t store, const char *filename)
554 {
555 struct stat st;
556 FILE *fp = NULL;
557 int fd = 0;
558
559 if (filename && hash_table_count (store->table) > 0)
560 {
561 fp = fopen (filename, "a+");
562 if (fp)
563 {
564 /* Lock the file to avoid potential race conditions */
565 fd = fileno (fp);
566 flock (fd, LOCK_EX);
567
568 /* If the file has changed, merge the changes with our in-memory data
569 before dumping them to the file.
570 Otherwise we could potentially overwrite the data stored by other Wget processes.
571 */
572 if (store->last_mtime && stat (filename, &st) == 0 && st.st_mtime > store->last_mtime)
573 hsts_read_database (store, fp, true);
574
575 /* We've merged the latest changes so we can now truncate the file
576 and dump everything. */
577 fseek (fp, 0, SEEK_SET);
578 ftruncate (fd, 0);
579
580 /* now dump to the file */
581 hsts_store_dump (store, fp);
582
583 /* fclose is expected to unlock the file for us */
584 fclose (fp);
585 }
586 }
587 }
588
589 bool
hsts_store_has_changed(hsts_store_t store)590 hsts_store_has_changed (hsts_store_t store)
591 {
592 return (store ? store->changed : false);
593 }
594
595 void
hsts_store_close(hsts_store_t store)596 hsts_store_close (hsts_store_t store)
597 {
598 hash_table_iterator it;
599
600 /* free all the host fields */
601 for (hash_table_iterate (store->table, &it); hash_table_iter_next (&it);)
602 {
603 xfree (((struct hsts_kh *) it.key)->host);
604 xfree (it.key);
605 xfree (it.value);
606 }
607
608 hash_table_destroy (store->table);
609 }
610
611 #ifdef TESTING
612 /* I know I'm really evil because I'm writing macros
613 that change control flow. But we're testing, who will tell? :D
614 */
615 #define TEST_URL_RW(s, u, p) do { \
616 if (test_url_rewrite (s, u, p, true)) \
617 return test_url_rewrite (s, u, p, true); \
618 } while (0)
619
620 #define TEST_URL_NORW(s, u, p) do { \
621 if (test_url_rewrite (s, u, p, false)) \
622 return test_url_rewrite (s, u, p, false); \
623 } while (0)
624
625 static char *
get_hsts_store_filename(void)626 get_hsts_store_filename (void)
627 {
628 char *filename = NULL;
629 FILE *fp = NULL;
630
631 if (opt.homedir)
632 {
633 filename = ajoin_dir_file (opt.homedir, ".wget-hsts-test");
634 fp = fopen (filename, "w");
635 if (fp)
636 fclose (fp);
637 }
638
639 return filename;
640 }
641
642 static hsts_store_t
open_hsts_test_store(void)643 open_hsts_test_store (void)
644 {
645 char *filename = NULL;
646 hsts_store_t table = NULL;
647
648 filename = get_hsts_store_filename ();
649 table = hsts_store_open (filename);
650 xfree (filename);
651
652 return table;
653 }
654
655 static void
close_hsts_test_store(hsts_store_t store)656 close_hsts_test_store (hsts_store_t store)
657 {
658 char *filename;
659
660 if ((filename = get_hsts_store_filename ()))
661 {
662 unlink (filename);
663 xfree (filename);
664 }
665 xfree (store);
666 }
667
668 static const char*
test_url_rewrite(hsts_store_t s,const char * url,int port,bool rewrite)669 test_url_rewrite (hsts_store_t s, const char *url, int port, bool rewrite)
670 {
671 bool result;
672 struct url u;
673
674 u.host = xstrdup (url);
675 u.port = port;
676 u.scheme = SCHEME_HTTP;
677
678 result = hsts_match (s, &u);
679
680 if (rewrite)
681 {
682 if (port == 80)
683 mu_assert("URL: port should've been rewritten to 443", u.port == 443);
684 else
685 mu_assert("URL: port should've been left intact", u.port == port);
686 mu_assert("URL: scheme should've been rewritten to HTTPS", u.scheme == SCHEME_HTTPS);
687 mu_assert("result should've been true", result == true);
688 }
689 else
690 {
691 mu_assert("URL: port should've been left intact", u.port == port);
692 mu_assert("URL: scheme should've been left intact", u.scheme == SCHEME_HTTP);
693 mu_assert("result should've been false", result == false);
694 }
695
696 xfree (u.host);
697 return NULL;
698 }
699
700 const char *
test_hsts_new_entry(void)701 test_hsts_new_entry (void)
702 {
703 enum hsts_kh_match match = NO_MATCH;
704 struct hsts_kh_info *khi;
705 hsts_store_t s;
706 bool created;
707
708 s = open_hsts_test_store ();
709 mu_assert("Could not open the HSTS store. This could be due to lack of memory.", s != NULL);
710
711 created = hsts_store_entry (s, SCHEME_HTTP, "www.foo.com", 80, 1234, true);
712 mu_assert("No entry should have been created.", created == false);
713
714 created = hsts_store_entry (s, SCHEME_HTTPS, "www.foo.com", 443, 1234, true);
715 mu_assert("A new entry should have been created", created == true);
716
717 khi = hsts_find_entry (s, "www.foo.com", MAKE_EXPLICIT_PORT (SCHEME_HTTPS, 443), &match, NULL);
718 mu_assert("Should've been a congruent match", match == CONGRUENT_MATCH);
719 mu_assert("No valid HSTS info was returned", khi != NULL);
720 mu_assert("Variable 'max_age' should be 1234", khi->max_age == 1234);
721 mu_assert("Variable 'include_subdomains' should be asserted", khi->include_subdomains == true);
722
723 khi = hsts_find_entry (s, "b.www.foo.com", MAKE_EXPLICIT_PORT (SCHEME_HTTPS, 443), &match, NULL);
724 mu_assert("Should've been a superdomain match", match == SUPERDOMAIN_MATCH);
725 mu_assert("No valid HSTS info was returned", khi != NULL);
726 mu_assert("Variable 'max_age' should be 1234", khi->max_age == 1234);
727 mu_assert("Variable 'include_subdomains' should be asserted", khi->include_subdomains == true);
728
729 khi = hsts_find_entry (s, "ww.foo.com", MAKE_EXPLICIT_PORT (SCHEME_HTTPS, 443), &match, NULL);
730 mu_assert("Should've been no match", match == NO_MATCH);
731
732 khi = hsts_find_entry (s, "foo.com", MAKE_EXPLICIT_PORT (SCHEME_HTTPS, 443), &match, NULL);
733 mu_assert("Should've been no match", match == NO_MATCH);
734
735 khi = hsts_find_entry (s, ".foo.com", MAKE_EXPLICIT_PORT (SCHEME_HTTPS, 443), &match, NULL);
736 mu_assert("Should've been no match", match == NO_MATCH);
737
738 khi = hsts_find_entry (s, ".www.foo.com", MAKE_EXPLICIT_PORT (SCHEME_HTTPS, 443), &match, NULL);
739 mu_assert("Should've been no match", match == NO_MATCH);
740
741 hsts_store_close (s);
742 close_hsts_test_store (s);
743
744 return NULL;
745 }
746
747 const char*
test_hsts_url_rewrite_superdomain(void)748 test_hsts_url_rewrite_superdomain (void)
749 {
750 hsts_store_t s;
751 bool created;
752
753 s = open_hsts_test_store ();
754 mu_assert("Could not open the HSTS store", s != NULL);
755
756 created = hsts_store_entry (s, SCHEME_HTTPS, "www.foo.com", 443, 1234, true);
757 mu_assert("A new entry should've been created", created == true);
758
759 TEST_URL_RW (s, "www.foo.com", 80);
760 TEST_URL_RW (s, "bar.www.foo.com", 80);
761
762 hsts_store_close (s);
763 close_hsts_test_store (s);
764
765 return NULL;
766 }
767
768 const char*
test_hsts_url_rewrite_congruent(void)769 test_hsts_url_rewrite_congruent (void)
770 {
771 hsts_store_t s;
772 bool created;
773
774 s = open_hsts_test_store ();
775 mu_assert("Could not open the HSTS store", s != NULL);
776
777 created = hsts_store_entry (s, SCHEME_HTTPS, "foo.com", 443, 1234, false);
778 mu_assert("A new entry should've been created", created == true);
779
780 TEST_URL_RW (s, "foo.com", 80);
781 TEST_URL_NORW (s, "www.foo.com", 80);
782
783 hsts_store_close (s);
784 close_hsts_test_store (s);
785
786 return NULL;
787 }
788
789 const char*
test_hsts_read_database(void)790 test_hsts_read_database (void)
791 {
792 hsts_store_t table;
793 char *file = NULL;
794 FILE *fp = NULL;
795 time_t created = time(NULL) - 10;
796
797 if (opt.homedir)
798 {
799 file = ajoin_dir_file (opt.homedir, ".wget-hsts-testing");
800 fp = fopen (file, "w");
801 if (fp)
802 {
803 fputs ("# dummy comment\n", fp);
804 fprintf (fp, "foo.example.com\t0\t1\t%lu\t123\n",(unsigned long) created);
805 fprintf (fp, "bar.example.com\t0\t0\t%lu\t456\n", (unsigned long) created);
806 fprintf (fp, "test.example.com\t8080\t0\t%lu\t789\n", (unsigned long) created);
807 fclose (fp);
808
809 table = hsts_store_open (file);
810
811 TEST_URL_RW (table, "foo.example.com", 80);
812 TEST_URL_RW (table, "www.foo.example.com", 80);
813 TEST_URL_RW (table, "bar.example.com", 80);
814
815 TEST_URL_NORW(table, "www.bar.example.com", 80);
816
817 TEST_URL_RW (table, "test.example.com", 8080);
818
819 hsts_store_close (table);
820 close_hsts_test_store (table);
821 unlink (file);
822 }
823 xfree (file);
824 }
825
826 return NULL;
827 }
828 #endif /* TESTING */
829 #endif /* HAVE_HSTS */
830