diff --git a/docs/libcurl/opts/CURLOPT_HSTSREADFUNCTION.md b/docs/libcurl/opts/CURLOPT_HSTSREADFUNCTION.md index 67129f242d..b5a685b73b 100644 --- a/docs/libcurl/opts/CURLOPT_HSTSREADFUNCTION.md +++ b/docs/libcurl/opts/CURLOPT_HSTSREADFUNCTION.md @@ -42,23 +42,26 @@ Pass a pointer to your callback function, as the prototype shows above. This callback function gets called by libcurl repeatedly when it populates the in-memory HSTS cache. -Set the *clientp* argument with the CURLOPT_HSTSREADDATA(3) option -or it is NULL. +Set the *clientp* argument with the CURLOPT_HSTSREADDATA(3) option or it is +NULL. -When this callback is invoked, the *sts* pointer points to a populated -struct: Copy the hostname to *name* (no longer than *namelen* -bytes). Make it null-terminated. Set *includeSubDomains* to TRUE or -FALSE. Set *expire* to a date stamp or a zero length string for *forever* -(wrong date stamp format might cause the name to not get accepted) +When this callback is invoked, the *sts* pointer points to a populated struct: +Copy the hostname to *name* (no longer than *namelen* bytes). Make it +null-terminated. Set *includeSubDomains* to TRUE or FALSE. Set *expire* to a +date stamp or a zero length string for *forever* (wrong date stamp format +might cause the name to not get accepted) -The callback should return *CURLSTS_OK* if it returns a name and is -prepared to be called again (for another host) or *CURLSTS_DONE* if it has -no entry to return. It can also return *CURLSTS_FAIL* to signal -error. Returning *CURLSTS_FAIL* stops the transfer from being performed -and make *CURLE_ABORTED_BY_CALLBACK* get returned. +The callback should return *CURLSTS_OK* if it returns a name and is prepared +to be called again (for another host) or *CURLSTS_DONE* if it has no entry to +return. It can also return *CURLSTS_FAIL* to signal error. Returning +*CURLSTS_FAIL* stops the transfer from being performed and make +*CURLE_ABORTED_BY_CALLBACK* get returned. -This option does not enable HSTS, you need to use CURLOPT_HSTS_CTRL(3) to -do that. +This option does not enable HSTS, you need to use CURLOPT_HSTS_CTRL(3) to do +that. + +The hostname provided to libcurl *should not* have a trailing dot nor leading +dot. # DEFAULT diff --git a/lib/hsts.c b/lib/hsts.c index ff21ad98d5..400b4423da 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -40,7 +40,7 @@ #define MAX_HSTS_LINE 4095 #define MAX_HSTS_HOSTLEN 2048 -#define MAX_HSTS_DATELEN 256 +#define MAX_HSTS_DATELEN 17 #define UNLIMITED "unlimited" #if defined(DEBUGBUILD) || defined(UNITTESTS) @@ -399,6 +399,61 @@ skipsave: return result; } +/* only returns SERIOUS errors */ +static CURLcode hsts_add_host_expire(struct hsts *h, + const char *host, size_t hostlen, + const char *expire, size_t explen, + bool subdomain) /* default */ +{ + CURLcode result = CURLE_OK; + struct stsentry *e; + char dbuf[MAX_HSTS_DATELEN + 1]; + time_t expires = 0; + time_t now = time(NULL); + + /* The date parser works on a null-terminated string. */ + if(explen > MAX_HSTS_DATELEN) + return CURLE_BAD_FUNCTION_ARGUMENT; + memcpy(dbuf, expire, explen); + dbuf[explen] = 0; + + if(!strcmp(dbuf, UNLIMITED)) + expires = TIME_T_MAX; + else + Curl_getdate_capped(dbuf, &expires); + + if(expires <= now) + /* this entry already expired */ + return CURLE_OK; + + if(host[0] == '.') { + host++; + hostlen--; + subdomain = TRUE; + } + if(hostlen && (host[hostlen - 1] == '.')) + /* strip off any trailing dot */ + hostlen--; + + if(hostlen) { + /* only add it if not already present */ + e = Curl_hsts(h, host, hostlen, subdomain); + if(!e) + result = hsts_create(h, host, hostlen, subdomain, expires); + /* 'host' is not necessarily null terminated */ + else if((hostlen == strlen(e->host) && + curl_strnequal(host, e->host, hostlen))) { + /* the same hostname, use the largest expire time and keep the strictest + subdomain policy */ + if(expires > e->expires) + e->expires = expires; + if(subdomain) + e->includeSubDomains = TRUE; + } + } + return result; +} + /* only returns SERIOUS errors */ static CURLcode hsts_add(struct hsts *h, const char *line) { @@ -415,54 +470,9 @@ static CURLcode hsts_add(struct hsts *h, const char *line) curlx_str_newline(&line)) ; else { - CURLcode result = CURLE_OK; - bool subdomain = FALSE; - struct stsentry *e; - char dbuf[MAX_HSTS_DATELEN + 1]; - time_t expires = 0; - const char *hp = curlx_str(&host); - size_t hlen; - time_t now = time(NULL); - - /* The date parser works on a null-terminated string. The maximum length - is upheld by curlx_str_quotedword(). */ - memcpy(dbuf, curlx_str(&date), curlx_strlen(&date)); - dbuf[curlx_strlen(&date)] = 0; - - if(!strcmp(dbuf, UNLIMITED)) - expires = TIME_T_MAX; - else - Curl_getdate_capped(dbuf, &expires); - - if(expires <= now) - /* this entry already expired */ - return CURLE_OK; - - if(hp[0] == '.') { - curlx_str_nudge(&host, 1); - hp = curlx_str(&host); - subdomain = TRUE; - } - hlen = curlx_strlen(&host); - if(hlen && (hp[hlen - 1] == '.')) - /* strip off any trailing dot */ - curlx_str_trim(&host, 1); - - /* only add it if not already present */ - e = Curl_hsts(h, curlx_str(&host), curlx_strlen(&host), subdomain); - if(!e) - result = hsts_create(h, curlx_str(&host), curlx_strlen(&host), - subdomain, expires); - else if(curlx_str_casecompare(&host, e->host)) { - /* the same hostname, use the largest expire time and keep the - strictest subdomain policy */ - if(expires > e->expires) - e->expires = expires; - if(subdomain) - e->includeSubDomains = TRUE; - } - if(result) - return result; + return hsts_add_host_expire(h, curlx_str(&host), curlx_strlen(&host), + curlx_str(&date), curlx_strlen(&date), + FALSE); } return CURLE_OK; @@ -485,23 +495,23 @@ static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h) e.namelen = sizeof(buffer) - 1; e.includeSubDomains = FALSE; /* default */ e.expire[0] = 0; + e.expire[MAX_HSTS_DATELEN] = 0; e.name[0] = 0; /* to make it clean */ + e.name[MAX_HSTS_HOSTLEN] = 0; sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp); if(sc == CURLSTS_OK) { - time_t expires = 0; CURLcode result; - DEBUGASSERT(e.name[0]); - if(!e.name[0]) - /* bail out if no name was stored */ + const char *date = e.expire; + if(!e.name[0] || e.expire[MAX_HSTS_DATELEN] || + e.name[MAX_HSTS_HOSTLEN]) + /* bail out if no name was stored or if a null terminator is gone */ return CURLE_BAD_FUNCTION_ARGUMENT; - if(e.expire[0]) - Curl_getdate_capped(e.expire, &expires); - else - expires = TIME_T_MAX; /* the end of time */ - result = hsts_create(h, e.name, strlen(e.name), - /* bitfield to bool conversion: */ - e.includeSubDomains ? TRUE : FALSE, - expires); + if(!date[0]) + date = UNLIMITED; + result = hsts_add_host_expire(h, e.name, strlen(e.name), + date, strlen(date), + /* bitfield to bool conversion: */ + e.includeSubDomains ? TRUE : FALSE); if(result) return result; } diff --git a/tests/data/test1915 b/tests/data/test1915 index 0b993c9ea4..8f9089b294 100644 --- a/tests/data/test1915 +++ b/tests/data/test1915 @@ -42,13 +42,13 @@ http://%HOSTIP:%NOLISTENPORT/not-there/%TESTNUMBER %if large-time [0/4] 1.example.com 25250320 01:02:03 [1/4] 2.example.com 25250320 03:02:01 -[2/4] 3.example.com 25250319 01:02:03 +[2/4] .3.example.com 25250319 01:02:03 %else [0/4] 1.example.com 20370320 01:02:03 [1/4] 2.example.com 20370320 03:02:01 -[2/4] 3.example.com 20370319 01:02:03 +[2/4] .3.example.com 20370319 01:02:03 %endif -[3/4] 4.example.com unlimited +[3/4] .4.example.com unlimited First request returned 7 Second request returned 42 diff --git a/tests/libtest/lib1915.c b/tests/libtest/lib1915.c index 393d6edcb7..d5dd4dc2fc 100644 --- a/tests/libtest/lib1915.c +++ b/tests/libtest/lib1915.c @@ -40,14 +40,17 @@ static CURLSTScode hstsread(CURL *curl, struct curl_hstsentry *e, void *userp) static const struct entry preload_hosts[] = { #if (SIZEOF_TIME_T < 5) { "1.example.com", "20370320 01:02:03" }, - { "2.example.com", "20370320 03:02:01" }, + { "2.example.com.", "20370320 03:02:01" }, { "3.example.com", "20370319 01:02:03" }, + { ".3.example.com", "20270319 01:02:03" }, #else { "1.example.com", "25250320 01:02:03" }, - { "2.example.com", "25250320 03:02:01" }, + { "2.example.com.", "25250320 03:02:01" }, { "3.example.com", "25250319 01:02:03" }, + { ".3.example.com", "22250319 01:02:03" }, #endif - { "4.example.com", "" }, + { "4.example.com", "" }, /* forever */ + { ".4.example.com", "20370319 01:02:03" }, { NULL, NULL } /* end of list marker */ }; @@ -85,7 +88,8 @@ static CURLSTScode hstswrite(CURL *curl, struct curl_hstsentry *e, { (void)curl; (void)userp; - curl_mprintf("[%zu/%zu] %s %s\n", i->index, i->total, e->name, e->expire); + curl_mprintf("[%zu/%zu] %s%s %s\n", i->index, i->total, + e->includeSubDomains ? "." : "", e->name, e->expire); return CURLSTS_OK; }