hsts: add read/write callbacks

- read/write callback options
- man pages for the 4 new setopts
- test 1915 verifies the callbacks

Closes #5896
This commit is contained in:
Daniel Stenberg 2020-11-02 23:17:01 +01:00
parent 7385610d0c
commit 2cfc4ed983
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
27 changed files with 652 additions and 22 deletions

View file

@ -22,7 +22,7 @@
#include "curl_setup.h"
#if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_ALTSVC)) || \
#if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_ALTSVC) || \
defined(USE_HSTS)
#include "curl_get_line.h"

View file

@ -888,7 +888,9 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data)
if(!outcurl->hsts)
goto fail;
if(outcurl->set.str[STRING_HSTS])
(void)Curl_hsts_load(outcurl->hsts, outcurl->set.str[STRING_HSTS]);
(void)Curl_hsts_loadfile(outcurl,
outcurl->hsts, outcurl->set.str[STRING_HSTS]);
(void)Curl_hsts_loadcb(outcurl, outcurl->hsts);
}
#endif
/* Clone the resolver handle, if present, for the new handle */

View file

@ -116,6 +116,10 @@ struct curl_easyoption Curl_easyopts[] = {
{"HEADERFUNCTION", CURLOPT_HEADERFUNCTION, CURLOT_FUNCTION, 0},
{"HEADEROPT", CURLOPT_HEADEROPT, CURLOT_VALUES, 0},
{"HSTS", CURLOPT_HSTS, CURLOT_STRING, 0},
{"HSTSREADDATA", CURLOPT_HSTSREADDATA, CURLOT_CBPTR, 0},
{"HSTSREADFUNCTION", CURLOPT_HSTSREADFUNCTION, CURLOT_FUNCTION, 0},
{"HSTSWRITEDATA", CURLOPT_HSTSWRITEDATA, CURLOT_CBPTR, 0},
{"HSTSWRITEFUNCTION", CURLOPT_HSTSWRITEFUNCTION, CURLOT_FUNCTION, 0},
{"HSTS_CTRL", CURLOPT_HSTS_CTRL, CURLOT_LONG, 0},
{"HTTP09_ALLOWED", CURLOPT_HTTP09_ALLOWED, CURLOT_LONG, 0},
{"HTTP200ALIASES", CURLOPT_HTTP200ALIASES, CURLOT_SLIST, 0},
@ -344,6 +348,6 @@ struct curl_easyoption Curl_easyopts[] = {
*/
int Curl_easyopts_check(void)
{
return (CURLOPT_LASTENTRY != (300 + 1));
return (CURLOPT_LASTENTRY != (304 + 1));
}
#endif

View file

@ -261,6 +261,37 @@ struct stsentry *Curl_hsts(struct hsts *h, const char *hostname,
return NULL; /* no match */
}
/*
* Send this HSTS entry to the write callback.
*/
static CURLcode hsts_push(struct Curl_easy *data,
struct curl_index *i,
struct stsentry *sts,
bool *stop)
{
struct curl_hstsentry e;
CURLSTScode sc;
struct tm stamp;
CURLcode result;
e.name = (char *)sts->host;
e.namelen = strlen(sts->host);
e.includeSubDomains = sts->includeSubDomains;
result = Curl_gmtime(sts->expires, &stamp);
if(result)
return result;
msnprintf(e.expire, sizeof(e.expire), "%d%02d%02d %02d:%02d:%02d",
stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
stamp.tm_hour, stamp.tm_min, stamp.tm_sec);
sc = data->set.hsts_write(data, &e, i,
data->set.hsts_write_userp);
*stop = (sc != CURLSTS_OK);
return sc == CURLSTS_FAIL ? CURLE_BAD_FUNCTION_ARGUMENT : CURLE_OK;
}
/*
* Write this single hsts entry to a single output line
*/
@ -280,7 +311,7 @@ static CURLcode hsts_out(struct stsentry *sts, FILE *fp)
/*
* Curl_https_save() writes the HSTS cache to a file.
* Curl_https_save() writes the HSTS cache to file and callback.
*/
CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
const char *file)
@ -302,7 +333,7 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
if((h->flags & CURLHSTS_READONLYFILE) || !file || !file[0])
/* marked as read-only, no file or zero length file name */
return CURLE_OK;
goto skipsave;
if(Curl_rand_hex(data, randsuffix, sizeof(randsuffix)))
return CURLE_FAILED_INIT;
@ -333,6 +364,22 @@ CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
unlink(tempstore);
}
free(tempstore);
skipsave:
if(data->set.hsts_write) {
/* if there's a write callback */
struct curl_index i; /* count */
i.total = h->list.size;
i.index = 0;
for(e = h->list.head; e; e = n) {
struct stsentry *sts = e->ptr;
bool stop;
n = e->next;
result = hsts_push(data, &i, sts, &stop);
if(result || stop)
break;
i.index++;
}
}
return result;
}
@ -367,6 +414,46 @@ static CURLcode hsts_add(struct hsts *h, char *line)
return CURLE_OK;
}
/*
* Load HSTS data from callback.
*
*/
static CURLcode hsts_pull(struct Curl_easy *data, struct hsts *h)
{
/* if the HSTS read callback is set, use it */
if(data->set.hsts_read) {
CURLSTScode sc;
DEBUGASSERT(h);
do {
char buffer[257];
struct curl_hstsentry e;
e.name = buffer;
e.namelen = sizeof(buffer)-1;
e.includeSubDomains = FALSE; /* default */
e.expire[0] = 0;
e.name[0] = 0; /* just to make it clean */
sc = data->set.hsts_read(data, &e, data->set.hsts_read_userp);
if(sc == CURLSTS_OK) {
time_t expires;
CURLcode result;
if(!e.name[0])
/* bail out if no name was stored */
return CURLE_BAD_FUNCTION_ARGUMENT;
if(e.expire[0])
expires = Curl_getdate_capped(e.expire);
else
expires = TIME_T_MAX; /* the end of time */
result = hsts_create(h, e.name, e.includeSubDomains, expires);
if(result)
return result;
}
else if(sc == CURLSTS_FAIL)
return CURLE_BAD_FUNCTION_ARGUMENT;
} while(sc == CURLSTS_OK);
}
return CURLE_OK;
}
/*
* Load the HSTS cache from the given file. The text based line-oriented file
* format is documented here:
@ -417,14 +504,22 @@ static CURLcode hsts_load(struct hsts *h, const char *file)
}
/*
* Curl_hsts_load() loads HSTS from file.
* Curl_hsts_loadfile() loads HSTS from file
*/
CURLcode Curl_hsts_load(struct hsts *h, const char *file)
CURLcode Curl_hsts_loadfile(struct Curl_easy *data,
struct hsts *h, const char *file)
{
CURLcode result;
DEBUGASSERT(h);
result = hsts_load(h, file);
return result;
(void)data;
return hsts_load(h, file);
}
/*
* Curl_hsts_loadcb() loads HSTS from callback
*/
CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h)
{
return hsts_pull(data, h);
}
#endif /* CURL_DISABLE_HTTP || USE_HSTS */

View file

@ -53,8 +53,13 @@ struct stsentry *Curl_hsts(struct hsts *h, const char *hostname,
bool subdomain);
CURLcode Curl_hsts_save(struct Curl_easy *data, struct hsts *h,
const char *file);
CURLcode Curl_hsts_load(struct hsts *h, const char *file);
CURLcode Curl_hsts_loadfile(struct Curl_easy *data,
struct hsts *h, const char *file);
CURLcode Curl_hsts_loadcb(struct Curl_easy *data,
struct hsts *h);
#else
#define Curl_hsts_cleanup(x)
#define Curl_hsts_loadcb(x,y)
#define Curl_hsts_save(x,y,z)
#endif /* CURL_DISABLE_HTTP || USE_HSTS */
#endif /* HEADER_CURL_HSTS_H */

View file

@ -2841,6 +2841,18 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
#endif
break;
#ifdef USE_HSTS
case CURLOPT_HSTSREADFUNCTION:
data->set.hsts_read = va_arg(param, curl_hstsread_callback);
break;
case CURLOPT_HSTSREADDATA:
data->set.hsts_read_userp = va_arg(param, void *);
break;
case CURLOPT_HSTSWRITEFUNCTION:
data->set.hsts_write = va_arg(param, curl_hstswrite_callback);
break;
case CURLOPT_HSTSWRITEDATA:
data->set.hsts_write_userp = va_arg(param, void *);
break;
case CURLOPT_HSTS:
if(!data->hsts) {
data->hsts = Curl_hsts_init();
@ -2852,7 +2864,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
if(result)
return result;
if(argptr)
(void)Curl_hsts_load(data->hsts, argptr);
(void)Curl_hsts_loadfile(data, data->hsts, argptr);
break;
case CURLOPT_HSTS_CTRL:
arg = va_arg(param, long);

View file

@ -78,6 +78,7 @@
#include "mime.h"
#include "strcase.h"
#include "urlapi-int.h"
#include "hsts.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@ -1528,6 +1529,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
}
#endif
Curl_http2_init_state(&data->state);
Curl_hsts_loadcb(data, data->hsts);
}
return result;

View file

@ -412,6 +412,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
Curl_flush_cookies(data, TRUE);
Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]);
Curl_altsvc_cleanup(&data->asi);
Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]);
Curl_hsts_cleanup(&data->hsts);
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
Curl_http_auth_cleanup_digest(data);

View file

@ -1639,7 +1639,12 @@ struct UserDefined {
curl_conv_callback convtonetwork;
/* function to convert from UTF-8 encoding: */
curl_conv_callback convfromutf8;
#ifdef USE_HSTS
curl_hstsread_callback hsts_read;
void *hsts_read_userp;
curl_hstswrite_callback hsts_write;
void *hsts_write_userp;
#endif
void *progress_client; /* pointer to pass to the progress callback */
void *ioctl_client; /* pointer to pass to the ioctl callback */
long timeout; /* in milliseconds, 0 means no timeout */