113498266Sopenharmony_ci/***************************************************************************
213498266Sopenharmony_ci *                                  _   _ ____  _
313498266Sopenharmony_ci *  Project                     ___| | | |  _ \| |
413498266Sopenharmony_ci *                             / __| | | | |_) | |
513498266Sopenharmony_ci *                            | (__| |_| |  _ <| |___
613498266Sopenharmony_ci *                             \___|\___/|_| \_\_____|
713498266Sopenharmony_ci *
813498266Sopenharmony_ci * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
913498266Sopenharmony_ci *
1013498266Sopenharmony_ci * This software is licensed as described in the file COPYING, which
1113498266Sopenharmony_ci * you should have received as part of this distribution. The terms
1213498266Sopenharmony_ci * are also available at https://curl.se/docs/copyright.html.
1313498266Sopenharmony_ci *
1413498266Sopenharmony_ci * You may opt to use, copy, modify, merge, publish, distribute and/or sell
1513498266Sopenharmony_ci * copies of the Software, and permit persons to whom the Software is
1613498266Sopenharmony_ci * furnished to do so, under the terms of the COPYING file.
1713498266Sopenharmony_ci *
1813498266Sopenharmony_ci * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
1913498266Sopenharmony_ci * KIND, either express or implied.
2013498266Sopenharmony_ci *
2113498266Sopenharmony_ci * SPDX-License-Identifier: curl
2213498266Sopenharmony_ci *
2313498266Sopenharmony_ci ***************************************************************************/
2413498266Sopenharmony_ci/*
2513498266Sopenharmony_ci * The Strict-Transport-Security header is defined in RFC 6797:
2613498266Sopenharmony_ci * https://datatracker.ietf.org/doc/html/rfc6797
2713498266Sopenharmony_ci */
2813498266Sopenharmony_ci#include "curl_setup.h"
2913498266Sopenharmony_ci
3013498266Sopenharmony_ci#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HSTS)
3113498266Sopenharmony_ci#include <curl/curl.h>
3213498266Sopenharmony_ci#include "urldata.h"
3313498266Sopenharmony_ci#include "llist.h"
3413498266Sopenharmony_ci#include "hsts.h"
3513498266Sopenharmony_ci#include "curl_get_line.h"
3613498266Sopenharmony_ci#include "strcase.h"
3713498266Sopenharmony_ci#include "sendf.h"
3813498266Sopenharmony_ci#include "strtoofft.h"
3913498266Sopenharmony_ci#include "parsedate.h"
4013498266Sopenharmony_ci#include "fopen.h"
4113498266Sopenharmony_ci#include "rename.h"
4213498266Sopenharmony_ci#include "share.h"
4313498266Sopenharmony_ci#include "strdup.h"
4413498266Sopenharmony_ci
4513498266Sopenharmony_ci/* The last 3 #include files should be in this order */
4613498266Sopenharmony_ci#include "curl_printf.h"
4713498266Sopenharmony_ci#include "curl_memory.h"
4813498266Sopenharmony_ci#include "memdebug.h"
4913498266Sopenharmony_ci
5013498266Sopenharmony_ci#define MAX_HSTS_LINE 4095
5113498266Sopenharmony_ci#define MAX_HSTS_HOSTLEN 256
5213498266Sopenharmony_ci#define MAX_HSTS_HOSTLENSTR "256"
5313498266Sopenharmony_ci#define MAX_HSTS_DATELEN 64
5413498266Sopenharmony_ci#define MAX_HSTS_DATELENSTR "64"
5513498266Sopenharmony_ci#define UNLIMITED "unlimited"
5613498266Sopenharmony_ci
5713498266Sopenharmony_ci#ifdef DEBUGBUILD
5813498266Sopenharmony_ci/* to play well with debug builds, we can *set* a fixed time this will
5913498266Sopenharmony_ci   return */
6013498266Sopenharmony_citime_t deltatime; /* allow for "adjustments" for unit test purposes */
6113498266Sopenharmony_cistatic time_t hsts_debugtime(void *unused)
6213498266Sopenharmony_ci{
6313498266Sopenharmony_ci  char *timestr = getenv("CURL_TIME");
6413498266Sopenharmony_ci  (void)unused;
6513498266Sopenharmony_ci  if(timestr) {
6613498266Sopenharmony_ci    curl_off_t val;
6713498266Sopenharmony_ci    (void)curlx_strtoofft(timestr, NULL, 10, &val);
6813498266Sopenharmony_ci
6913498266Sopenharmony_ci    val += (curl_off_t)deltatime;
7013498266Sopenharmony_ci    return (time_t)val;
7113498266Sopenharmony_ci  }
7213498266Sopenharmony_ci  return time(NULL);
7313498266Sopenharmony_ci}
7413498266Sopenharmony_ci#undef time
7513498266Sopenharmony_ci#define time(x) hsts_debugtime(x)
7613498266Sopenharmony_ci#endif
7713498266Sopenharmony_ci
7813498266Sopenharmony_cistruct hsts *Curl_hsts_init(void)
7913498266Sopenharmony_ci{
8013498266Sopenharmony_ci  struct hsts *h = calloc(1, sizeof(struct hsts));
8113498266Sopenharmony_ci  if(h) {
8213498266Sopenharmony_ci    Curl_llist_init(&h->list, NULL);
8313498266Sopenharmony_ci  }
8413498266Sopenharmony_ci  return h;
8513498266Sopenharmony_ci}
8613498266Sopenharmony_ci
8713498266Sopenharmony_cistatic void hsts_free(struct stsentry *e)
8813498266Sopenharmony_ci{
8913498266Sopenharmony_ci  free((char *)e->host);
9013498266Sopenharmony_ci  free(e);
9113498266Sopenharmony_ci}
9213498266Sopenharmony_ci
9313498266Sopenharmony_civoid Curl_hsts_cleanup(struct hsts **hp)
9413498266Sopenharmony_ci{
9513498266Sopenharmony_ci  struct hsts *h = *hp;
9613498266Sopenharmony_ci  if(h) {
9713498266Sopenharmony_ci    struct Curl_llist_element *e;
9813498266Sopenharmony_ci    struct Curl_llist_element *n;
9913498266Sopenharmony_ci    for(e = h->list.head; e; e = n) {
10013498266Sopenharmony_ci      struct stsentry *sts = e->ptr;
10113498266Sopenharmony_ci      n = e->next;
10213498266Sopenharmony_ci      hsts_free(sts);
10313498266Sopenharmony_ci    }
10413498266Sopenharmony_ci    free(h->filename);
10513498266Sopenharmony_ci    free(h);
10613498266Sopenharmony_ci    *hp = NULL;
10713498266Sopenharmony_ci  }
10813498266Sopenharmony_ci}
10913498266Sopenharmony_ci
11013498266Sopenharmony_cistatic struct stsentry *hsts_entry(void)
11113498266Sopenharmony_ci{
11213498266Sopenharmony_ci  return calloc(1, sizeof(struct stsentry));
11313498266Sopenharmony_ci}
11413498266Sopenharmony_ci
11513498266Sopenharmony_cistatic CURLcode hsts_create(struct hsts *h,
11613498266Sopenharmony_ci                            const char *hostname,
11713498266Sopenharmony_ci                            bool subdomains,
11813498266Sopenharmony_ci                            curl_off_t expires)
11913498266Sopenharmony_ci{
12013498266Sopenharmony_ci  size_t hlen;
12113498266Sopenharmony_ci  DEBUGASSERT(h);
12213498266Sopenharmony_ci  DEBUGASSERT(hostname);
12313498266Sopenharmony_ci
12413498266Sopenharmony_ci  hlen = strlen(hostname);
12513498266Sopenharmony_ci  if(hlen && (hostname[hlen - 1] == '.'))
12613498266Sopenharmony_ci    /* strip off any trailing dot */
12713498266Sopenharmony_ci    --hlen;
12813498266Sopenharmony_ci  if(hlen) {
12913498266Sopenharmony_ci    char *duphost;
13013498266Sopenharmony_ci    struct stsentry *sts = hsts_entry();
13113498266Sopenharmony_ci    if(!sts)
13213498266Sopenharmony_ci      return CURLE_OUT_OF_MEMORY;
13313498266Sopenharmony_ci
13413498266Sopenharmony_ci    duphost = Curl_memdup0(hostname, hlen);
13513498266Sopenharmony_ci    if(!duphost) {
13613498266Sopenharmony_ci      free(sts);
13713498266Sopenharmony_ci      return CURLE_OUT_OF_MEMORY;
13813498266Sopenharmony_ci    }
13913498266Sopenharmony_ci
14013498266Sopenharmony_ci    sts->host = duphost;
14113498266Sopenharmony_ci    sts->expires = expires;
14213498266Sopenharmony_ci    sts->includeSubDomains = subdomains;
14313498266Sopenharmony_ci    Curl_llist_insert_next(&h->list, h->list.tail, sts, &sts->node);
14413498266Sopenharmony_ci  }
14513498266Sopenharmony_ci  return CURLE_OK;
14613498266Sopenharmony_ci}
14713498266Sopenharmony_ci
14813498266Sopenharmony_ciCURLcode Curl_hsts_parse(struct hsts *h, const char *hostname,
14913498266Sopenharmony_ci                         const char *header)
15013498266Sopenharmony_ci{
15113498266Sopenharmony_ci  const char *p = header;
15213498266Sopenharmony_ci  curl_off_t expires = 0;
15313498266Sopenharmony_ci  bool gotma = FALSE;
15413498266Sopenharmony_ci  bool gotinc = FALSE;
15513498266Sopenharmony_ci  bool subdomains = FALSE;
15613498266Sopenharmony_ci  struct stsentry *sts;
15713498266Sopenharmony_ci  time_t now = time(NULL);
15813498266Sopenharmony_ci
15913498266Sopenharmony_ci  if(Curl_host_is_ipnum(hostname))
16013498266Sopenharmony_ci    /* "explicit IP address identification of all forms is excluded."
16113498266Sopenharmony_ci       / RFC 6797 */
16213498266Sopenharmony_ci    return CURLE_OK;
16313498266Sopenharmony_ci
16413498266Sopenharmony_ci  do {
16513498266Sopenharmony_ci    while(*p && ISBLANK(*p))
16613498266Sopenharmony_ci      p++;
16713498266Sopenharmony_ci    if(strncasecompare("max-age=", p, 8)) {
16813498266Sopenharmony_ci      bool quoted = FALSE;
16913498266Sopenharmony_ci      CURLofft offt;
17013498266Sopenharmony_ci      char *endp;
17113498266Sopenharmony_ci
17213498266Sopenharmony_ci      if(gotma)
17313498266Sopenharmony_ci        return CURLE_BAD_FUNCTION_ARGUMENT;
17413498266Sopenharmony_ci
17513498266Sopenharmony_ci      p += 8;
17613498266Sopenharmony_ci      while(*p && ISBLANK(*p))
17713498266Sopenharmony_ci        p++;
17813498266Sopenharmony_ci      if(*p == '\"') {
17913498266Sopenharmony_ci        p++;
18013498266Sopenharmony_ci        quoted = TRUE;
18113498266Sopenharmony_ci      }
18213498266Sopenharmony_ci      offt = curlx_strtoofft(p, &endp, 10, &expires);
18313498266Sopenharmony_ci      if(offt == CURL_OFFT_FLOW)
18413498266Sopenharmony_ci        expires = CURL_OFF_T_MAX;
18513498266Sopenharmony_ci      else if(offt)
18613498266Sopenharmony_ci        /* invalid max-age */
18713498266Sopenharmony_ci        return CURLE_BAD_FUNCTION_ARGUMENT;
18813498266Sopenharmony_ci      p = endp;
18913498266Sopenharmony_ci      if(quoted) {
19013498266Sopenharmony_ci        if(*p != '\"')
19113498266Sopenharmony_ci          return CURLE_BAD_FUNCTION_ARGUMENT;
19213498266Sopenharmony_ci        p++;
19313498266Sopenharmony_ci      }
19413498266Sopenharmony_ci      gotma = TRUE;
19513498266Sopenharmony_ci    }
19613498266Sopenharmony_ci    else if(strncasecompare("includesubdomains", p, 17)) {
19713498266Sopenharmony_ci      if(gotinc)
19813498266Sopenharmony_ci        return CURLE_BAD_FUNCTION_ARGUMENT;
19913498266Sopenharmony_ci      subdomains = TRUE;
20013498266Sopenharmony_ci      p += 17;
20113498266Sopenharmony_ci      gotinc = TRUE;
20213498266Sopenharmony_ci    }
20313498266Sopenharmony_ci    else {
20413498266Sopenharmony_ci      /* unknown directive, do a lame attempt to skip */
20513498266Sopenharmony_ci      while(*p && (*p != ';'))
20613498266Sopenharmony_ci        p++;
20713498266Sopenharmony_ci    }
20813498266Sopenharmony_ci
20913498266Sopenharmony_ci    while(*p && ISBLANK(*p))
21013498266Sopenharmony_ci      p++;
21113498266Sopenharmony_ci    if(*p == ';')
21213498266Sopenharmony_ci      p++;
21313498266Sopenharmony_ci  } while(*p);
21413498266Sopenharmony_ci
21513498266Sopenharmony_ci  if(!gotma)
21613498266Sopenharmony_ci    /* max-age is mandatory */
21713498266Sopenharmony_ci    return CURLE_BAD_FUNCTION_ARGUMENT;
21813498266Sopenharmony_ci
21913498266Sopenharmony_ci  if(!expires) {
22013498266Sopenharmony_ci    /* remove the entry if present verbatim (without subdomain match) */
22113498266Sopenharmony_ci    sts = Curl_hsts(h, hostname, FALSE);
22213498266Sopenharmony_ci    if(sts) {
22313498266Sopenharmony_ci      Curl_llist_remove(&h->list, &sts->node, NULL);
22413498266Sopenharmony_ci      hsts_free(sts);
22513498266Sopenharmony_ci    }
22613498266Sopenharmony_ci    return CURLE_OK;
22713498266Sopenharmony_ci  }
22813498266Sopenharmony_ci
22913498266Sopenharmony_ci  if(CURL_OFF_T_MAX - now < expires)
23013498266Sopenharmony_ci    /* would overflow, use maximum value */
23113498266Sopenharmony_ci    expires = CURL_OFF_T_MAX;
23213498266Sopenharmony_ci  else
23313498266Sopenharmony_ci    expires += now;
23413498266Sopenharmony_ci
23513498266Sopenharmony_ci  /* check if it already exists */
23613498266Sopenharmony_ci  sts = Curl_hsts(h, hostname, FALSE);
23713498266Sopenharmony_ci  if(sts) {
23813498266Sopenharmony_ci    /* just update these fields */
23913498266Sopenharmony_ci    sts->expires = expires;
24013498266Sopenharmony_ci    sts->includeSubDomains = subdomains;
24113498266Sopenharmony_ci  }
24213498266Sopenharmony_ci  else
24313498266Sopenharmony_ci    return hsts_create(h, hostname, subdomains, expires);
24413498266Sopenharmony_ci
24513498266Sopenharmony_ci  return CURLE_OK;
24613498266Sopenharmony_ci}
24713498266Sopenharmony_ci
24813498266Sopenharmony_ci/*
24913498266Sopenharmony_ci * Return TRUE if the given host name is currently an HSTS one.
25013498266Sopenharmony_ci *
25113498266Sopenharmony_ci * The 'subdomain' argument tells the function if subdomain matching should be
25213498266Sopenharmony_ci * attempted.
25313498266Sopenharmony_ci */
25413498266Sopenharmony_cistruct stsentry *Curl_hsts(struct hsts *h, const char *hostname,
25513498266Sopenharmony_ci                           bool subdomain)
25613498266Sopenharmony_ci{
25713498266Sopenharmony_ci  if(h) {
25813498266Sopenharmony_ci    char buffer[MAX_HSTS_HOSTLEN + 1];
25913498266Sopenharmony_ci    time_t now = time(NULL);
26013498266Sopenharmony_ci    size_t hlen = strlen(hostname);
26113498266Sopenharmony_ci    struct Curl_llist_element *e;
26213498266Sopenharmony_ci    struct Curl_llist_element *n;
26313498266Sopenharmony_ci
26413498266Sopenharmony_ci    if((hlen > MAX_HSTS_HOSTLEN) || !hlen)
26513498266Sopenharmony_ci      return NULL;
26613498266Sopenharmony_ci    memcpy(buffer, hostname, hlen);
26713498266Sopenharmony_ci    if(hostname[hlen-1] == '.')
26813498266Sopenharmony_ci      /* remove the trailing dot */
26913498266Sopenharmony_ci      --hlen;
27013498266Sopenharmony_ci    buffer[hlen] = 0;
27113498266Sopenharmony_ci    hostname = buffer;
27213498266Sopenharmony_ci
27313498266Sopenharmony_ci    for(e = h->list.head; e; e = n) {
27413498266Sopenharmony_ci      struct stsentry *sts = e->ptr;
27513498266Sopenharmony_ci      n = e->next;
27613498266Sopenharmony_ci      if(sts->expires <= now) {
27713498266Sopenharmony_ci        /* remove expired entries */
27813498266Sopenharmony_ci        Curl_llist_remove(&h->list, &sts->node, NULL);
27913498266Sopenharmony_ci        hsts_free(sts);
28013498266Sopenharmony_ci        continue;
28113498266Sopenharmony_ci      }
28213498266Sopenharmony_ci      if(subdomain && sts->includeSubDomains) {
28313498266Sopenharmony_ci        size_t ntail = strlen(sts->host);
28413498266Sopenharmony_ci        if(ntail < hlen) {
28513498266Sopenharmony_ci          size_t offs = hlen - ntail;
28613498266Sopenharmony_ci          if((hostname[offs-1] == '.') &&
28713498266Sopenharmony_ci             strncasecompare(&hostname[offs], sts->host, ntail))
28813498266Sopenharmony_ci            return sts;
28913498266Sopenharmony_ci        }
29013498266Sopenharmony_ci      }
29113498266Sopenharmony_ci      if(strcasecompare(hostname, sts->host))
29213498266Sopenharmony_ci        return sts;
29313498266Sopenharmony_ci    }
29413498266Sopenharmony_ci  }
29513498266Sopenharmony_ci  return NULL; /* no match */
29613498266Sopenharmony_ci}
29713498266Sopenharmony_ci
29813498266Sopenharmony_ci/*
29913498266Sopenharmony_ci * Send this HSTS entry to the write callback.
30013498266Sopenharmony_ci */
30113498266Sopenharmony_cistatic CURLcode hsts_push(struct Curl_easy *data,
30213498266Sopenharmony_ci                          struct curl_index *i,
30313498266Sopenharmony_ci                          struct stsentry *sts,
30413498266Sopenharmony_ci                          bool *stop)
30513498266Sopenharmony_ci{
30613498266Sopenharmony_ci  struct curl_hstsentry e;
30713498266Sopenharmony_ci  CURLSTScode sc;
30813498266Sopenharmony_ci  struct tm stamp;
30913498266Sopenharmony_ci  CURLcode result;
31013498266Sopenharmony_ci
31113498266Sopenharmony_ci  e.name = (char *)sts->host;
31213498266Sopenharmony_ci  e.namelen = strlen(sts->host);
31313498266Sopenharmony_ci  e.includeSubDomains = sts->includeSubDomains;
31413498266Sopenharmony_ci
31513498266Sopenharmony_ci  if(sts->expires != TIME_T_MAX) {
31613498266Sopenharmony_ci    result = Curl_gmtime((time_t)sts->expires, &stamp);
31713498266Sopenharmony_ci    if(result)
31813498266Sopenharmony_ci      return result;
31913498266Sopenharmony_ci
32013498266Sopenharmony_ci    msnprintf(e.expire, sizeof(e.expire), "%d%02d%02d %02d:%02d:%02d",
32113498266Sopenharmony_ci              stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
32213498266Sopenharmony_ci              stamp.tm_hour, stamp.tm_min, stamp.tm_sec);
32313498266Sopenharmony_ci  }
32413498266Sopenharmony_ci  else
32513498266Sopenharmony_ci    strcpy(e.expire, UNLIMITED);
32613498266Sopenharmony_ci
32713498266Sopenharmony_ci  sc = data->set.hsts_write(data, &e, i,
32813498266Sopenharmony_ci                            data->set.hsts_write_userp);
32913498266Sopenharmony_ci  *stop = (sc != CURLSTS_OK);
33013498266Sopenharmony_ci  return sc == CURLSTS_FAIL ? CURLE_BAD_FUNCTION_ARGUMENT : CURLE_OK;
33113498266Sopenharmony_ci}
33213498266Sopenharmony_ci
33313498266Sopenharmony_ci/*
33413498266Sopenharmony_ci * Write this single hsts entry to a single output line
33513498266Sopenharmony_ci */
33613498266Sopenharmony_cistatic CURLcode hsts_out(struct stsentry *sts, FILE *fp)
33713498266Sopenharmony_ci{
33813498266Sopenharmony_ci  struct tm stamp;
33913498266Sopenharmony_ci  if(sts->expires != TIME_T_MAX) {
34013498266Sopenharmony_ci    CURLcode result = Curl_gmtime((time_t)sts->expires, &stamp);
34113498266Sopenharmony_ci    if(result)
34213498266Sopenharmony_ci      return result;
34313498266Sopenharmony_ci    fprintf(fp, "%s%s \"%d%02d%02d %02d:%02d:%02d\"\n",
34413498266Sopenharmony_ci            sts->includeSubDomains ? ".": "", sts->host,
34513498266Sopenharmony_ci            stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
34613498266Sopenharmony_ci            stamp.tm_hour, stamp.tm_min, stamp.tm_sec);
34713498266Sopenharmony_ci  }
34813498266Sopenharmony_ci  else
34913498266Sopenharmony_ci    fprintf(fp, "%s%s \"%s\"\n",
35013498266Sopenharmony_ci            sts->includeSubDomains ? ".": "", sts->host, UNLIMITED);
35113498266Sopenharmony_ci  return CURLE_OK;
35213498266Sopenharmony_ci}
35313498266Sopenharmony_ci
35413498266Sopenharmony_ci
35513498266Sopenharmony_ci/*
35613498266Sopenharmony_ci * Curl_https_save() writes the HSTS cache to file and callback.
35713498266Sopenharmony_ci */
35813498266Sopenharmony_ciCURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
35913498266Sopenharmony_ci                        const char *file)
36013498266Sopenharmony_ci{
36113498266Sopenharmony_ci  struct Curl_llist_element *e;
36213498266Sopenharmony_ci  struct Curl_llist_element *n;
36313498266Sopenharmony_ci  CURLcode result = CURLE_OK;
36413498266Sopenharmony_ci  FILE *out;
36513498266Sopenharmony_ci  char *tempstore = NULL;
36613498266Sopenharmony_ci
36713498266Sopenharmony_ci  if(!h)
36813498266Sopenharmony_ci    /* no cache activated */
36913498266Sopenharmony_ci    return CURLE_OK;
37013498266Sopenharmony_ci
37113498266Sopenharmony_ci  /* if no new name is given, use the one we stored from the load */
37213498266Sopenharmony_ci  if(!file && h->filename)
37313498266Sopenharmony_ci    file = h->filename;
37413498266Sopenharmony_ci
37513498266Sopenharmony_ci  if((h->flags & CURLHSTS_READONLYFILE) || !file || !file[0])
37613498266Sopenharmony_ci    /* marked as read-only, no file or zero length file name */
37713498266Sopenharmony_ci    goto skipsave;
37813498266Sopenharmony_ci
37913498266Sopenharmony_ci  result = Curl_fopen(data, file, &out, &tempstore);
38013498266Sopenharmony_ci  if(!result) {
38113498266Sopenharmony_ci    fputs("# Your HSTS cache. https://curl.se/docs/hsts.html\n"
38213498266Sopenharmony_ci          "# This file was generated by libcurl! Edit at your own risk.\n",
38313498266Sopenharmony_ci          out);
38413498266Sopenharmony_ci    for(e = h->list.head; e; e = n) {
38513498266Sopenharmony_ci      struct stsentry *sts = e->ptr;
38613498266Sopenharmony_ci      n = e->next;
38713498266Sopenharmony_ci      result = hsts_out(sts, out);
38813498266Sopenharmony_ci      if(result)
38913498266Sopenharmony_ci        break;
39013498266Sopenharmony_ci    }
39113498266Sopenharmony_ci    fclose(out);
39213498266Sopenharmony_ci    if(!result && tempstore && Curl_rename(tempstore, file))
39313498266Sopenharmony_ci      result = CURLE_WRITE_ERROR;
39413498266Sopenharmony_ci
39513498266Sopenharmony_ci    if(result && tempstore)
39613498266Sopenharmony_ci      unlink(tempstore);
39713498266Sopenharmony_ci  }
39813498266Sopenharmony_ci  free(tempstore);
39913498266Sopenharmony_ciskipsave:
40013498266Sopenharmony_ci  if(data->set.hsts_write) {
40113498266Sopenharmony_ci    /* if there's a write callback */
40213498266Sopenharmony_ci    struct curl_index i; /* count */
40313498266Sopenharmony_ci    i.total = h->list.size;
40413498266Sopenharmony_ci    i.index = 0;
40513498266Sopenharmony_ci    for(e = h->list.head; e; e = n) {
40613498266Sopenharmony_ci      struct stsentry *sts = e->ptr;
40713498266Sopenharmony_ci      bool stop;
40813498266Sopenharmony_ci      n = e->next;
40913498266Sopenharmony_ci      result = hsts_push(data, &i, sts, &stop);
41013498266Sopenharmony_ci      if(result || stop)
41113498266Sopenharmony_ci        break;
41213498266Sopenharmony_ci      i.index++;
41313498266Sopenharmony_ci    }
41413498266Sopenharmony_ci  }
41513498266Sopenharmony_ci  return result;
41613498266Sopenharmony_ci}
41713498266Sopenharmony_ci
41813498266Sopenharmony_ci/* only returns SERIOUS errors */
41913498266Sopenharmony_cistatic CURLcode hsts_add(struct hsts *h, char *line)
42013498266Sopenharmony_ci{
42113498266Sopenharmony_ci  /* Example lines:
42213498266Sopenharmony_ci     example.com "20191231 10:00:00"
42313498266Sopenharmony_ci     .example.net "20191231 10:00:00"
42413498266Sopenharmony_ci   */
42513498266Sopenharmony_ci  char host[MAX_HSTS_HOSTLEN + 1];
42613498266Sopenharmony_ci  char date[MAX_HSTS_DATELEN + 1];
42713498266Sopenharmony_ci  int rc;
42813498266Sopenharmony_ci
42913498266Sopenharmony_ci  rc = sscanf(line,
43013498266Sopenharmony_ci              "%" MAX_HSTS_HOSTLENSTR "s \"%" MAX_HSTS_DATELENSTR "[^\"]\"",
43113498266Sopenharmony_ci              host, date);
43213498266Sopenharmony_ci  if(2 == rc) {
43313498266Sopenharmony_ci    time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) :
43413498266Sopenharmony_ci      TIME_T_MAX;
43513498266Sopenharmony_ci    CURLcode result = CURLE_OK;
43613498266Sopenharmony_ci    char *p = host;
43713498266Sopenharmony_ci    bool subdomain = FALSE;
43813498266Sopenharmony_ci    struct stsentry *e;
43913498266Sopenharmony_ci    if(p[0] == '.') {
44013498266Sopenharmony_ci      p++;
44113498266Sopenharmony_ci      subdomain = TRUE;
44213498266Sopenharmony_ci    }
44313498266Sopenharmony_ci    /* only add it if not already present */
44413498266Sopenharmony_ci    e = Curl_hsts(h, p, subdomain);
44513498266Sopenharmony_ci    if(!e)
44613498266Sopenharmony_ci      result = hsts_create(h, p, subdomain, expires);
44713498266Sopenharmony_ci    else {
44813498266Sopenharmony_ci      /* the same host name, use the largest expire time */
44913498266Sopenharmony_ci      if(expires > e->expires)
45013498266Sopenharmony_ci        e->expires = expires;
45113498266Sopenharmony_ci    }
45213498266Sopenharmony_ci    if(result)
45313498266Sopenharmony_ci      return result;
45413498266Sopenharmony_ci  }
45513498266Sopenharmony_ci
45613498266Sopenharmony_ci  return CURLE_OK;
45713498266Sopenharmony_ci}
45813498266Sopenharmony_ci
45913498266Sopenharmony_ci/*
46013498266Sopenharmony_ci * Load HSTS data from callback.
46113498266Sopenharmony_ci *
46213498266Sopenharmony_ci */
46313498266Sopenharmony_cistatic CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h)
46413498266Sopenharmony_ci{
46513498266Sopenharmony_ci  /* if the HSTS read callback is set, use it */
46613498266Sopenharmony_ci  if(data->set.hsts_read) {
46713498266Sopenharmony_ci    CURLSTScode sc;
46813498266Sopenharmony_ci    DEBUGASSERT(h);
46913498266Sopenharmony_ci    do {
47013498266Sopenharmony_ci      char buffer[MAX_HSTS_HOSTLEN + 1];
47113498266Sopenharmony_ci      struct curl_hstsentry e;
47213498266Sopenharmony_ci      e.name = buffer;
47313498266Sopenharmony_ci      e.namelen = sizeof(buffer)-1;
47413498266Sopenharmony_ci      e.includeSubDomains = FALSE; /* default */
47513498266Sopenharmony_ci      e.expire[0] = 0;
47613498266Sopenharmony_ci      e.name[0] = 0; /* just to make it clean */
47713498266Sopenharmony_ci      sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp);
47813498266Sopenharmony_ci      if(sc == CURLSTS_OK) {
47913498266Sopenharmony_ci        time_t expires;
48013498266Sopenharmony_ci        CURLcode result;
48113498266Sopenharmony_ci        DEBUGASSERT(e.name[0]);
48213498266Sopenharmony_ci        if(!e.name[0])
48313498266Sopenharmony_ci          /* bail out if no name was stored */
48413498266Sopenharmony_ci          return CURLE_BAD_FUNCTION_ARGUMENT;
48513498266Sopenharmony_ci        if(e.expire[0])
48613498266Sopenharmony_ci          expires = Curl_getdate_capped(e.expire);
48713498266Sopenharmony_ci        else
48813498266Sopenharmony_ci          expires = TIME_T_MAX; /* the end of time */
48913498266Sopenharmony_ci        result = hsts_create(h, e.name,
49013498266Sopenharmony_ci                             /* bitfield to bool conversion: */
49113498266Sopenharmony_ci                             e.includeSubDomains ? TRUE : FALSE,
49213498266Sopenharmony_ci                             expires);
49313498266Sopenharmony_ci        if(result)
49413498266Sopenharmony_ci          return result;
49513498266Sopenharmony_ci      }
49613498266Sopenharmony_ci      else if(sc == CURLSTS_FAIL)
49713498266Sopenharmony_ci        return CURLE_ABORTED_BY_CALLBACK;
49813498266Sopenharmony_ci    } while(sc == CURLSTS_OK);
49913498266Sopenharmony_ci  }
50013498266Sopenharmony_ci  return CURLE_OK;
50113498266Sopenharmony_ci}
50213498266Sopenharmony_ci
50313498266Sopenharmony_ci/*
50413498266Sopenharmony_ci * Load the HSTS cache from the given file. The text based line-oriented file
50513498266Sopenharmony_ci * format is documented here: https://curl.se/docs/hsts.html
50613498266Sopenharmony_ci *
50713498266Sopenharmony_ci * This function only returns error on major problems that prevent hsts
50813498266Sopenharmony_ci * handling to work completely. It will ignore individual syntactical errors
50913498266Sopenharmony_ci * etc.
51013498266Sopenharmony_ci */
51113498266Sopenharmony_cistatic CURLcode hsts_load(struct hsts *h, const char *file)
51213498266Sopenharmony_ci{
51313498266Sopenharmony_ci  CURLcode result = CURLE_OK;
51413498266Sopenharmony_ci  char *line = NULL;
51513498266Sopenharmony_ci  FILE *fp;
51613498266Sopenharmony_ci
51713498266Sopenharmony_ci  /* we need a private copy of the file name so that the hsts cache file
51813498266Sopenharmony_ci     name survives an easy handle reset */
51913498266Sopenharmony_ci  free(h->filename);
52013498266Sopenharmony_ci  h->filename = strdup(file);
52113498266Sopenharmony_ci  if(!h->filename)
52213498266Sopenharmony_ci    return CURLE_OUT_OF_MEMORY;
52313498266Sopenharmony_ci
52413498266Sopenharmony_ci  fp = fopen(file, FOPEN_READTEXT);
52513498266Sopenharmony_ci  if(fp) {
52613498266Sopenharmony_ci    line = malloc(MAX_HSTS_LINE);
52713498266Sopenharmony_ci    if(!line)
52813498266Sopenharmony_ci      goto fail;
52913498266Sopenharmony_ci    while(Curl_get_line(line, MAX_HSTS_LINE, fp)) {
53013498266Sopenharmony_ci      char *lineptr = line;
53113498266Sopenharmony_ci      while(*lineptr && ISBLANK(*lineptr))
53213498266Sopenharmony_ci        lineptr++;
53313498266Sopenharmony_ci      if(*lineptr == '#')
53413498266Sopenharmony_ci        /* skip commented lines */
53513498266Sopenharmony_ci        continue;
53613498266Sopenharmony_ci
53713498266Sopenharmony_ci      hsts_add(h, lineptr);
53813498266Sopenharmony_ci    }
53913498266Sopenharmony_ci    free(line); /* free the line buffer */
54013498266Sopenharmony_ci    fclose(fp);
54113498266Sopenharmony_ci  }
54213498266Sopenharmony_ci  return result;
54313498266Sopenharmony_ci
54413498266Sopenharmony_cifail:
54513498266Sopenharmony_ci  Curl_safefree(h->filename);
54613498266Sopenharmony_ci  fclose(fp);
54713498266Sopenharmony_ci  return CURLE_OUT_OF_MEMORY;
54813498266Sopenharmony_ci}
54913498266Sopenharmony_ci
55013498266Sopenharmony_ci/*
55113498266Sopenharmony_ci * Curl_hsts_loadfile() loads HSTS from file
55213498266Sopenharmony_ci */
55313498266Sopenharmony_ciCURLcode Curl_hsts_loadfile(struct Curl_easy *data,
55413498266Sopenharmony_ci                            struct hsts *h, const char *file)
55513498266Sopenharmony_ci{
55613498266Sopenharmony_ci  DEBUGASSERT(h);
55713498266Sopenharmony_ci  (void)data;
55813498266Sopenharmony_ci  return hsts_load(h, file);
55913498266Sopenharmony_ci}
56013498266Sopenharmony_ci
56113498266Sopenharmony_ci/*
56213498266Sopenharmony_ci * Curl_hsts_loadcb() loads HSTS from callback
56313498266Sopenharmony_ci */
56413498266Sopenharmony_ciCURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h)
56513498266Sopenharmony_ci{
56613498266Sopenharmony_ci  if(h)
56713498266Sopenharmony_ci    return hsts_pull(data, h);
56813498266Sopenharmony_ci  return CURLE_OK;
56913498266Sopenharmony_ci}
57013498266Sopenharmony_ci
57113498266Sopenharmony_civoid Curl_hsts_loadfiles(struct Curl_easy *data)
57213498266Sopenharmony_ci{
57313498266Sopenharmony_ci  struct curl_slist *l = data->state.hstslist;
57413498266Sopenharmony_ci  if(l) {
57513498266Sopenharmony_ci    Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE);
57613498266Sopenharmony_ci
57713498266Sopenharmony_ci    while(l) {
57813498266Sopenharmony_ci      (void)Curl_hsts_loadfile(data, data->hsts, l->data);
57913498266Sopenharmony_ci      l = l->next;
58013498266Sopenharmony_ci    }
58113498266Sopenharmony_ci    Curl_share_unlock(data, CURL_LOCK_DATA_HSTS);
58213498266Sopenharmony_ci  }
58313498266Sopenharmony_ci}
58413498266Sopenharmony_ci
58513498266Sopenharmony_ci#endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
586