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