mirror of
https://github.com/curl/curl.git
synced 2026-08-24 21:43:32 +03:00
alt-svc: the libcurl bits
This commit is contained in:
parent
4331a3b8fa
commit
e1be825453
25 changed files with 1180 additions and 32 deletions
|
|
@ -55,7 +55,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \
|
|||
curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \
|
||||
x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \
|
||||
mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \
|
||||
doh.c urlapi.c
|
||||
doh.c urlapi.c altsvc.c
|
||||
|
||||
LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
|
||||
formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \
|
||||
|
|
@ -75,7 +75,8 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
|
|||
curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \
|
||||
x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \
|
||||
curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \
|
||||
curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h
|
||||
curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h \
|
||||
altsvc.h
|
||||
|
||||
LIB_RCFILES = libcurl.rc
|
||||
|
||||
|
|
|
|||
569
lib/altsvc.c
Normal file
569
lib/altsvc.c
Normal file
|
|
@ -0,0 +1,569 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* Project ___| | | | _ \| |
|
||||
* / __| | | | |_) | |
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
* are also available at https://curl.haxx.se/docs/copyright.html.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of the Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the COPYING file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
***************************************************************************/
|
||||
/*
|
||||
* The Alt-Svc: header is defined in RFC 7838:
|
||||
* https://tools.ietf.org/html/rfc7838
|
||||
*/
|
||||
#include "curl_setup.h"
|
||||
|
||||
#if !defined(CURL_DISABLE_HTTP) && defined(USE_ALTSVC)
|
||||
#include <curl/curl.h>
|
||||
#include "urldata.h"
|
||||
#include "altsvc.h"
|
||||
#include "cookie.h" /* for Curl_get_line() */
|
||||
#include "strcase.h"
|
||||
#include "parsedate.h"
|
||||
#include "sendf.h"
|
||||
#include "warnless.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
#include "curl_memory.h"
|
||||
#include "memdebug.h"
|
||||
|
||||
#define MAX_ALTSVC_LINE 4095
|
||||
#define MAX_ALTSVC_DATELENSTR "64"
|
||||
#define MAX_ALTSVC_DATELEN 64
|
||||
#define MAX_ALTSVC_HOSTLENSTR "512"
|
||||
#define MAX_ALTSVC_HOSTLEN 512
|
||||
#define MAX_ALTSVC_ALPNLENSTR "10"
|
||||
#define MAX_ALTSVC_ALPNLEN 10
|
||||
|
||||
static enum alpnid alpn2alpnid(char *name)
|
||||
{
|
||||
if(strcasecompare(name, "h1"))
|
||||
return ALPN_h1;
|
||||
if(strcasecompare(name, "h2"))
|
||||
return ALPN_h2;
|
||||
if(strcasecompare(name, "h2c"))
|
||||
return ALPN_h2c;
|
||||
if(strcasecompare(name, "h3"))
|
||||
return ALPN_h3;
|
||||
return ALPN_none; /* unknown, probably rubbish input */
|
||||
}
|
||||
|
||||
/* Given the ALPN ID, return the name */
|
||||
const char *Curl_alpnid2str(enum alpnid id)
|
||||
{
|
||||
switch(id) {
|
||||
case ALPN_h1:
|
||||
return "h1";
|
||||
case ALPN_h2:
|
||||
return "h2";
|
||||
case ALPN_h2c:
|
||||
return "h2c";
|
||||
case ALPN_h3:
|
||||
return "h3";
|
||||
default:
|
||||
return ""; /* bad */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void altsvc_free(struct altsvc *as)
|
||||
{
|
||||
free(as->srchost);
|
||||
free(as->dsthost);
|
||||
free(as);
|
||||
}
|
||||
|
||||
static struct altsvc *altsvc_createid(const char *srchost,
|
||||
const char *dsthost,
|
||||
enum alpnid srcalpnid,
|
||||
enum alpnid dstalpnid,
|
||||
unsigned int srcport,
|
||||
unsigned int dstport)
|
||||
{
|
||||
struct altsvc *as = calloc(sizeof(struct altsvc), 1);
|
||||
if(!as)
|
||||
return NULL;
|
||||
|
||||
as->srchost = strdup(srchost);
|
||||
if(!as->srchost)
|
||||
goto error;
|
||||
as->dsthost = strdup(dsthost);
|
||||
if(!as->dsthost)
|
||||
goto error;
|
||||
|
||||
as->srcalpnid = srcalpnid;
|
||||
as->dstalpnid = dstalpnid;
|
||||
as->srcport = curlx_ultous(srcport);
|
||||
as->dstport = curlx_ultous(dstport);
|
||||
|
||||
return as;
|
||||
error:
|
||||
altsvc_free(as);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct altsvc *altsvc_create(char *srchost,
|
||||
char *dsthost,
|
||||
char *srcalpn,
|
||||
char *dstalpn,
|
||||
unsigned int srcport,
|
||||
unsigned int dstport)
|
||||
{
|
||||
enum alpnid dstalpnid = alpn2alpnid(dstalpn);
|
||||
enum alpnid srcalpnid = alpn2alpnid(srcalpn);
|
||||
if(!srcalpnid || !dstalpnid)
|
||||
return NULL;
|
||||
return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid,
|
||||
srcport, dstport);
|
||||
}
|
||||
|
||||
/* only returns SERIOUS errors */
|
||||
static CURLcode altsvc_add(struct altsvcinfo *asi, char *line)
|
||||
{
|
||||
/* Example line:
|
||||
h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1
|
||||
*/
|
||||
char srchost[MAX_ALTSVC_HOSTLEN + 1];
|
||||
char dsthost[MAX_ALTSVC_HOSTLEN + 1];
|
||||
char srcalpn[MAX_ALTSVC_ALPNLEN + 1];
|
||||
char dstalpn[MAX_ALTSVC_ALPNLEN + 1];
|
||||
char date[MAX_ALTSVC_DATELEN + 1];
|
||||
unsigned int srcport;
|
||||
unsigned int dstport;
|
||||
unsigned int prio;
|
||||
unsigned int persist;
|
||||
int rc;
|
||||
|
||||
rc = sscanf(line,
|
||||
"%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
|
||||
"%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
|
||||
"\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u",
|
||||
srcalpn, srchost, &srcport,
|
||||
dstalpn, dsthost, &dstport,
|
||||
date, &persist, &prio);
|
||||
if(9 == rc) {
|
||||
struct altsvc *as;
|
||||
time_t expires = curl_getdate(date, NULL);
|
||||
as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport);
|
||||
if(as) {
|
||||
as->expires = expires;
|
||||
as->prio = prio;
|
||||
as->persist = persist ? 1 : 0;
|
||||
Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
|
||||
asi->num++; /* one more entry */
|
||||
}
|
||||
}
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load alt-svc entries from the given file. The text based line-oriented file
|
||||
* format is documented here:
|
||||
* https://github.com/curl/curl/wiki/QUIC-implementation
|
||||
*
|
||||
* This function only returns error on major problems that prevents alt-svc
|
||||
* handling to work completely. It will ignore individual syntactical errors
|
||||
* etc.
|
||||
*/
|
||||
static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
char *line = NULL;
|
||||
FILE *fp = fopen(file, FOPEN_READTEXT);
|
||||
if(fp) {
|
||||
line = malloc(MAX_ALTSVC_LINE);
|
||||
if(!line)
|
||||
goto fail;
|
||||
while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) {
|
||||
char *lineptr = line;
|
||||
while(*lineptr && ISBLANK(*lineptr))
|
||||
lineptr++;
|
||||
if(*lineptr == '#')
|
||||
/* skip commented lines */
|
||||
continue;
|
||||
|
||||
altsvc_add(asi, lineptr);
|
||||
}
|
||||
free(line); /* free the line buffer */
|
||||
fclose(fp);
|
||||
}
|
||||
return result;
|
||||
|
||||
fail:
|
||||
free(line);
|
||||
fclose(fp);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write this single altsvc entry to a single output line
|
||||
*/
|
||||
|
||||
static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
|
||||
{
|
||||
struct tm stamp;
|
||||
Curl_gmtime(as->expires, &stamp);
|
||||
|
||||
fprintf(fp,
|
||||
"%s %s %u "
|
||||
"%s %s %u "
|
||||
"\"%d%02d%02d "
|
||||
"%02d:%02d:%02d\" "
|
||||
"%u %d\n",
|
||||
Curl_alpnid2str(as->srcalpnid), as->srchost, as->srcport,
|
||||
Curl_alpnid2str(as->dstalpnid), as->dsthost, as->dstport,
|
||||
stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
|
||||
stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
|
||||
as->persist, as->prio);
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/* ---- library-wide functions below ---- */
|
||||
|
||||
/*
|
||||
* Curl_altsvc_init() creates a new altsvc cache.
|
||||
* It returns the new instance or NULL if something goes wrong.
|
||||
*/
|
||||
struct altsvcinfo *Curl_altsvc_init(void)
|
||||
{
|
||||
struct altsvcinfo *asi = calloc(sizeof(struct altsvcinfo), 1);
|
||||
if(!asi)
|
||||
return NULL;
|
||||
Curl_llist_init(&asi->list, NULL);
|
||||
|
||||
/* set default behavior */
|
||||
asi->flags = CURLALTSVC_H1
|
||||
#ifdef USE_NGHTTP2
|
||||
| CURLALTSVC_H2
|
||||
#endif
|
||||
#ifdef USE_HTTP3
|
||||
/* TODO: adjust when known */
|
||||
| CURLALTSVC_H3
|
||||
#endif
|
||||
;
|
||||
return asi;
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_altsvc_load() loads alt-svc from file.
|
||||
*/
|
||||
CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
|
||||
{
|
||||
CURLcode result;
|
||||
DEBUGASSERT(asi);
|
||||
result = altsvc_load(asi, file);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_altsvc_ctrl() passes on the external bitmask.
|
||||
*/
|
||||
CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)
|
||||
{
|
||||
DEBUGASSERT(asi);
|
||||
if(!ctrl)
|
||||
/* unexpected */
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
asi->flags = ctrl;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_altsvc_cleanup() frees an altsvc cache instance and all associated
|
||||
* resources.
|
||||
*/
|
||||
void Curl_altsvc_cleanup(struct altsvcinfo *altsvc)
|
||||
{
|
||||
struct curl_llist_element *e;
|
||||
struct curl_llist_element *n;
|
||||
if(altsvc) {
|
||||
for(e = altsvc->list.head; e; e = n) {
|
||||
struct altsvc *as = e->ptr;
|
||||
n = e->next;
|
||||
altsvc_free(as);
|
||||
}
|
||||
free(altsvc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_altsvc_save() writes the altsvc cache to a file.
|
||||
*/
|
||||
CURLcode Curl_altsvc_save(struct altsvcinfo *altsvc, const char *file)
|
||||
{
|
||||
struct curl_llist_element *e;
|
||||
struct curl_llist_element *n;
|
||||
CURLcode result = CURLE_OK;
|
||||
FILE *out;
|
||||
|
||||
if(!altsvc)
|
||||
/* no cache activated */
|
||||
return CURLE_OK;
|
||||
|
||||
if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file[0])
|
||||
/* marked as read-only or zero length file name */
|
||||
return CURLE_OK;
|
||||
out = fopen(file, FOPEN_WRITETEXT);
|
||||
if(!out)
|
||||
return CURLE_WRITE_ERROR;
|
||||
fputs("# Your alt-svc cache. https://curl.haxx.se/docs/alt-svc.html\n"
|
||||
"# This file was generated by libcurl! Edit at your own risk.\n",
|
||||
out);
|
||||
for(e = altsvc->list.head; e; e = n) {
|
||||
struct altsvc *as = e->ptr;
|
||||
n = e->next;
|
||||
result = altsvc_out(as, out);
|
||||
if(result)
|
||||
break;
|
||||
}
|
||||
fclose(out);
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
|
||||
{
|
||||
size_t len;
|
||||
const char *protop;
|
||||
const char *p = *ptr;
|
||||
while(*p && ISBLANK(*p))
|
||||
p++;
|
||||
protop = p;
|
||||
while(*p && ISALNUM(*p))
|
||||
p++;
|
||||
len = p - protop;
|
||||
|
||||
if(!len || (len >= buflen))
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT; /* TODO: improve error code */
|
||||
memcpy(alpnbuf, protop, len);
|
||||
alpnbuf[len] = 0;
|
||||
*ptr = p;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/* altsvc_flush() removes all alternatives for this source origin from the
|
||||
list */
|
||||
static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
|
||||
const char *srchost, unsigned short srcport)
|
||||
{
|
||||
struct curl_llist_element *e;
|
||||
struct curl_llist_element *n;
|
||||
for(e = asi->list.head; e; e = n) {
|
||||
struct altsvc *as = e->ptr;
|
||||
n = e->next;
|
||||
if((srcalpnid == as->srcalpnid) &&
|
||||
(srcport == as->srcport) &&
|
||||
strcasecompare(srchost, as->srchost)) {
|
||||
Curl_llist_remove(&asi->list, e, NULL);
|
||||
altsvc_free(as);
|
||||
asi->num--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUGBUILD
|
||||
/* to play well with debug builds, we can *set* a fixed time this will
|
||||
return */
|
||||
static time_t debugtime(void *unused)
|
||||
{
|
||||
char *timestr = getenv("CURL_TIME");
|
||||
(void)unused;
|
||||
if(timestr) {
|
||||
unsigned long val = strtol(timestr, NULL, 10);
|
||||
return (time_t)val;
|
||||
}
|
||||
return time(NULL);
|
||||
}
|
||||
#define time(x) debugtime(x)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Curl_altsvc_parse() takes an incoming alt-svc response header and stores
|
||||
* the data correctly in the cache.
|
||||
*
|
||||
* 'value' points to the header *value*. That's contents to the right of the
|
||||
* header name.
|
||||
*/
|
||||
CURLcode Curl_altsvc_parse(struct Curl_easy *data,
|
||||
struct altsvcinfo *asi, const char *value,
|
||||
enum alpnid srcalpnid, const char *srchost,
|
||||
unsigned short srcport)
|
||||
{
|
||||
const char *p = value;
|
||||
size_t len;
|
||||
enum alpnid dstalpnid = srcalpnid; /* the same by default */
|
||||
char namebuf[MAX_ALTSVC_HOSTLEN] = "";
|
||||
char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
|
||||
struct altsvc *as;
|
||||
unsigned short dstport = srcport; /* the same by default */
|
||||
const char *semip;
|
||||
time_t maxage = 24 * 3600; /* default is 24 hours */
|
||||
bool persist = FALSE;
|
||||
CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
|
||||
if(result)
|
||||
return result;
|
||||
|
||||
DEBUGASSERT(asi);
|
||||
|
||||
/* Flush all cached alternatives for this source origin, if any */
|
||||
altsvc_flush(asi, srcalpnid, srchost, srcport);
|
||||
|
||||
/* "clear" is a magic keyword */
|
||||
if(strcasecompare(alpnbuf, "clear")) {
|
||||
/* TODO: clear whatever it is it should clear */
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/* The 'ma' and 'persist' flags are annoyingly meant for all alternatives
|
||||
but are set after the list on the line. Scan for the semicolons and get
|
||||
those fields first! */
|
||||
semip = p;
|
||||
do {
|
||||
semip = strchr(semip, ';');
|
||||
if(semip) {
|
||||
char option[32];
|
||||
unsigned long num;
|
||||
char *end_ptr;
|
||||
semip++; /* pass the semicolon */
|
||||
result = getalnum(&semip, option, sizeof(option));
|
||||
if(result)
|
||||
break;
|
||||
while(*semip && ISBLANK(*semip))
|
||||
semip++;
|
||||
if(*semip != '=')
|
||||
continue;
|
||||
semip++;
|
||||
num = strtoul(semip, &end_ptr, 10);
|
||||
if(num < ULONG_MAX) {
|
||||
if(strcasecompare("ma", option))
|
||||
maxage = num;
|
||||
else if(strcasecompare("persist", option) && (num == 1))
|
||||
persist = TRUE;
|
||||
}
|
||||
semip = end_ptr;
|
||||
}
|
||||
} while(semip);
|
||||
|
||||
do {
|
||||
if(*p == '=') {
|
||||
/* [protocol]="[host][:port]" */
|
||||
dstalpnid = alpn2alpnid(alpnbuf);
|
||||
if(!dstalpnid) {
|
||||
infof(data, "Unknown alt-svc protocol \"%s\", ignoring...\n", alpnbuf);
|
||||
return CURLE_OK;
|
||||
}
|
||||
p++;
|
||||
if(*p == '\"') {
|
||||
const char *dsthost;
|
||||
p++;
|
||||
if(*p != ':') {
|
||||
/* host name starts here */
|
||||
const char *hostp = p;
|
||||
while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
|
||||
p++;
|
||||
len = p - hostp;
|
||||
if(!len || (len >= MAX_ALTSVC_HOSTLEN))
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT; /* TODO: improve error code */
|
||||
memcpy(namebuf, hostp, len);
|
||||
namebuf[len] = 0;
|
||||
dsthost = namebuf;
|
||||
}
|
||||
else {
|
||||
/* no destination name, use source host */
|
||||
dsthost = srchost;
|
||||
}
|
||||
if(*p == ':') {
|
||||
/* a port number */
|
||||
char *end_ptr;
|
||||
unsigned long port = strtoul(++p, &end_ptr, 10);
|
||||
if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
|
||||
infof(data, "Unknown alt-svc port number, ignoring...\n");
|
||||
return CURLE_OK;
|
||||
}
|
||||
p = end_ptr;
|
||||
dstport = curlx_ultous(port);
|
||||
}
|
||||
if(*p++ != '\"')
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
as = altsvc_createid(srchost, dsthost,
|
||||
srcalpnid, dstalpnid,
|
||||
srcport, dstport);
|
||||
if(as) {
|
||||
/* TODO: the expires time also needs to take the Age: value (if any)
|
||||
into account. [See RFC 7838 section 3.1] */
|
||||
as->expires = maxage + time(NULL);
|
||||
as->persist = persist;
|
||||
Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
|
||||
asi->num++; /* one more entry */
|
||||
infof(data, "Added alt-svc: %s:%d over %s\n", dsthost, dstport,
|
||||
Curl_alpnid2str(dstalpnid));
|
||||
}
|
||||
}
|
||||
/* after the double quote there can be a comma if there's another
|
||||
string or a semicolon if no more */
|
||||
if(*p == ',') {
|
||||
/* comma means another alternative is presented */
|
||||
p++;
|
||||
result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
|
||||
if(result)
|
||||
/* failed to parse, but since we alredy did at least one host we
|
||||
return OK */
|
||||
return CURLE_OK;
|
||||
}
|
||||
}
|
||||
} while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE on a match
|
||||
*/
|
||||
bool Curl_altsvc_lookup(struct altsvcinfo *asi,
|
||||
enum alpnid srcalpnid, const char *srchost,
|
||||
int srcport,
|
||||
enum alpnid *dstalpnid, const char **dsthost,
|
||||
int *dstport)
|
||||
{
|
||||
struct curl_llist_element *e;
|
||||
struct curl_llist_element *n;
|
||||
time_t now = time(NULL);
|
||||
DEBUGASSERT(asi);
|
||||
DEBUGASSERT(srchost);
|
||||
DEBUGASSERT(dsthost);
|
||||
|
||||
for(e = asi->list.head; e; e = n) {
|
||||
struct altsvc *as = e->ptr;
|
||||
n = e->next;
|
||||
if(as->expires < now) {
|
||||
/* an expired entry, remove */
|
||||
altsvc_free(as);
|
||||
continue;
|
||||
}
|
||||
if((as->srcalpnid == srcalpnid) &&
|
||||
strcasecompare(as->srchost, srchost) &&
|
||||
as->srcport == srcport) {
|
||||
/* match */
|
||||
*dstalpnid = as->dstalpnid;
|
||||
*dsthost = as->dsthost;
|
||||
*dstport = as->dstport;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#endif /* CURL_DISABLE_HTTP || USE_ALTSVC */
|
||||
77
lib/altsvc.h
Normal file
77
lib/altsvc.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#ifndef HEADER_CURL_ALTSVC_H
|
||||
#define HEADER_CURL_ALTSVC_H
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* Project ___| | | | _ \| |
|
||||
* / __| | | | |_) | |
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
* are also available at https://curl.haxx.se/docs/copyright.html.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of the Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the COPYING file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
***************************************************************************/
|
||||
#include "curl_setup.h"
|
||||
|
||||
#if !defined(CURL_DISABLE_HTTP) && defined(USE_ALTSVC)
|
||||
#include <curl/curl.h>
|
||||
#include "llist.h"
|
||||
|
||||
enum alpnid {
|
||||
ALPN_none,
|
||||
ALPN_h1,
|
||||
ALPN_h2,
|
||||
ALPN_h2c,
|
||||
ALPN_h3
|
||||
};
|
||||
|
||||
struct altsvc {
|
||||
char *srchost;
|
||||
char *dsthost;
|
||||
unsigned short srcport;
|
||||
unsigned short dstport;
|
||||
enum alpnid srcalpnid;
|
||||
enum alpnid dstalpnid;
|
||||
time_t expires;
|
||||
bool persist;
|
||||
int prio;
|
||||
struct curl_llist_element node;
|
||||
};
|
||||
|
||||
struct altsvcinfo {
|
||||
char *filename;
|
||||
struct curl_llist list; /* list of entries */
|
||||
size_t num; /* number of alt-svc entries */
|
||||
long flags; /* the publicly set bitmask */
|
||||
};
|
||||
|
||||
const char *Curl_alpnid2str(enum alpnid id);
|
||||
struct altsvcinfo *Curl_altsvc_init(void);
|
||||
CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file);
|
||||
CURLcode Curl_altsvc_save(struct altsvcinfo *asi, const char *file);
|
||||
CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl);
|
||||
void Curl_altsvc_cleanup(struct altsvcinfo *altsvc);
|
||||
CURLcode Curl_altsvc_parse(struct Curl_easy *data,
|
||||
struct altsvcinfo *altsvc, const char *value,
|
||||
enum alpnid srcalpn, const char *srchost,
|
||||
unsigned short srcport);
|
||||
bool Curl_altsvc_lookup(struct altsvcinfo *asi,
|
||||
enum alpnid srcalpnid, const char *srchost,
|
||||
int srcport,
|
||||
enum alpnid *dstalpnid, const char **dsthost,
|
||||
int *dstport);
|
||||
#else
|
||||
/* disabled */
|
||||
#define Curl_altsvc_save(a,b)
|
||||
#endif /* CURL_DISABLE_HTTP || USE_ALTSVC */
|
||||
#endif /* HEADER_CURL_ALTSVC_H */
|
||||
|
|
@ -1092,7 +1092,7 @@ Curl_cookie_add(struct Curl_easy *data,
|
|||
* get_line() makes sure to only return complete whole lines that fit in 'len'
|
||||
* bytes and end with a newline.
|
||||
*/
|
||||
static char *get_line(char *buf, int len, FILE *input)
|
||||
char *Curl_get_line(char *buf, int len, FILE *input)
|
||||
{
|
||||
bool partial = FALSE;
|
||||
while(1) {
|
||||
|
|
@ -1172,7 +1172,7 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
|
|||
line = malloc(MAX_COOKIE_LINE);
|
||||
if(!line)
|
||||
goto fail;
|
||||
while(get_line(line, MAX_COOKIE_LINE, fp)) {
|
||||
while(Curl_get_line(line, MAX_COOKIE_LINE, fp)) {
|
||||
if(checkprefix("Set-Cookie:", line)) {
|
||||
/* This is a cookie line, get it! */
|
||||
lineptr = &line[11];
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ struct Cookie *Curl_cookie_getlist(struct CookieInfo *, const char *,
|
|||
void Curl_cookie_freelist(struct Cookie *cookies);
|
||||
void Curl_cookie_clearall(struct CookieInfo *cookies);
|
||||
void Curl_cookie_clearsess(struct CookieInfo *cookies);
|
||||
char *Curl_get_line(char *buf, int len, FILE *input);
|
||||
|
||||
#if defined(CURL_DISABLE_HTTP) || defined(CURL_DISABLE_COOKIES)
|
||||
#define Curl_cookie_list(x) NULL
|
||||
|
|
|
|||
15
lib/http.c
15
lib/http.c
|
|
@ -77,6 +77,7 @@
|
|||
#include "http2.h"
|
||||
#include "connect.h"
|
||||
#include "strdup.h"
|
||||
#include "altsvc.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
|
|
@ -3978,6 +3979,20 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
|
|||
}
|
||||
}
|
||||
}
|
||||
#ifdef USE_ALTSVC
|
||||
/* If enabled, the header is incoming and this is over HTTPS */
|
||||
else if(data->asi && checkprefix("Alt-Svc:", k->p) &&
|
||||
(conn->handler->flags & PROTOPT_SSL)) {
|
||||
/* the ALPN of the current request */
|
||||
enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
|
||||
result = Curl_altsvc_parse(data, data->asi,
|
||||
&k->p[ strlen("Alt-Svc:") ],
|
||||
id, conn->host.name,
|
||||
curlx_uitous(conn->remote_port));
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
else if(conn->handler->protocol & CURLPROTO_RTSP) {
|
||||
result = Curl_rtsp_parseheader(conn, k->p);
|
||||
if(result)
|
||||
|
|
|
|||
26
lib/setopt.c
26
lib/setopt.c
|
|
@ -44,6 +44,7 @@
|
|||
#include "http2.h"
|
||||
#include "setopt.h"
|
||||
#include "multiif.h"
|
||||
#include "altsvc.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
|
|
@ -2655,6 +2656,31 @@ static CURLcode vsetopt(struct Curl_easy *data, CURLoption option,
|
|||
data->set.trailer_data = va_arg(param, void *);
|
||||
#endif
|
||||
break;
|
||||
#ifdef USE_ALTSVC
|
||||
case CURLOPT_ALTSVC:
|
||||
if(!data->asi) {
|
||||
data->asi = Curl_altsvc_init();
|
||||
if(!data->asi)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
argptr = va_arg(param, char *);
|
||||
result = Curl_setstropt(&data->set.str[STRING_ALTSVC], argptr);
|
||||
if(result)
|
||||
return result;
|
||||
(void)Curl_altsvc_load(data->asi, argptr);
|
||||
break;
|
||||
case CURLOPT_ALTSVC_CTRL:
|
||||
if(!data->asi) {
|
||||
data->asi = Curl_altsvc_init();
|
||||
if(!data->asi)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
arg = va_arg(param, long);
|
||||
result = Curl_altsvc_ctrl(data->asi, arg);
|
||||
if(result)
|
||||
return result;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
/* unknown tag and its companion, just ignore: */
|
||||
result = CURLE_UNKNOWN_OPTION;
|
||||
|
|
|
|||
34
lib/url.c
34
lib/url.c
|
|
@ -120,6 +120,7 @@ bool curl_win32_idn_to_ascii(const char *in, char **out);
|
|||
#include "dotdot.h"
|
||||
#include "strdup.h"
|
||||
#include "setopt.h"
|
||||
#include "altsvc.h"
|
||||
|
||||
/* The last 3 #include files should be in this order */
|
||||
#include "curl_printf.h"
|
||||
|
|
@ -374,6 +375,11 @@ CURLcode Curl_close(struct Curl_easy *data)
|
|||
Curl_safefree(data->state.headerbuff);
|
||||
Curl_safefree(data->state.ulbuf);
|
||||
Curl_flush_cookies(data, 1);
|
||||
#ifdef USE_ALTSVC
|
||||
Curl_altsvc_save(data->asi, data->set.str[STRING_ALTSVC]);
|
||||
Curl_altsvc_cleanup(data->asi);
|
||||
data->asi = NULL;
|
||||
#endif
|
||||
Curl_digest_cleanup(data);
|
||||
Curl_safefree(data->info.contenttype);
|
||||
Curl_safefree(data->info.wouldredirect);
|
||||
|
|
@ -3368,6 +3374,34 @@ static CURLcode parse_connect_to_slist(struct Curl_easy *data,
|
|||
conn_to_host = conn_to_host->next;
|
||||
}
|
||||
|
||||
#ifdef USE_ALTSVC
|
||||
if(data->asi && !host && (port == -1) &&
|
||||
(conn->handler->protocol == CURLPROTO_HTTPS)) {
|
||||
/* no connect_to match, try alt-svc! */
|
||||
const char *nhost;
|
||||
int nport;
|
||||
enum alpnid nalpnid;
|
||||
bool hit;
|
||||
host = conn->host.rawalloc;
|
||||
hit = Curl_altsvc_lookup(data->asi,
|
||||
ALPN_h1, host, conn->remote_port, /* from */
|
||||
&nalpnid, &nhost, &nport /* to */);
|
||||
if(hit) {
|
||||
char *hostd = strdup((char *)nhost);
|
||||
if(!hostd)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
conn->conn_to_host.rawalloc = hostd;
|
||||
conn->conn_to_host.name = hostd;
|
||||
conn->bits.conn_to_host = TRUE;
|
||||
conn->conn_to_port = nport;
|
||||
conn->bits.conn_to_port = TRUE;
|
||||
infof(data, "Alt-svc connecting from [%s]%s:%d to [%s]%s:%d\n",
|
||||
Curl_alpnid2str(ALPN_h1), host, conn->remote_port,
|
||||
Curl_alpnid2str(nalpnid), hostd, nport);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1478,6 +1478,9 @@ enum dupstring {
|
|||
#endif
|
||||
STRING_TARGET, /* CURLOPT_REQUEST_TARGET */
|
||||
STRING_DOH, /* CURLOPT_DOH_URL */
|
||||
#ifdef USE_ALTSVC
|
||||
STRING_ALTSVC, /* CURLOPT_ALTSVC */
|
||||
#endif
|
||||
/* -- end of zero-terminated strings -- */
|
||||
|
||||
STRING_LASTZEROTERMINATED,
|
||||
|
|
@ -1794,6 +1797,9 @@ struct Curl_easy {
|
|||
NOTE that the 'cookie' field in the
|
||||
UserDefined struct defines if the "engine"
|
||||
is to be used or not. */
|
||||
#ifdef USE_ALTSVC
|
||||
struct altsvcinfo *asi; /* the alt-svc cache */
|
||||
#endif
|
||||
struct Progress progress; /* for all the progress meter data */
|
||||
struct UrlState state; /* struct for fields used for state info and
|
||||
other dynamic purposes */
|
||||
|
|
|
|||
|
|
@ -369,6 +369,9 @@ static curl_version_info_data version_info = {
|
|||
#endif
|
||||
#if defined(HAVE_BROTLI)
|
||||
| CURL_VERSION_BROTLI
|
||||
#endif
|
||||
#if defined(USE_ALTSVC)
|
||||
| CURL_VERSION_ALTSVC
|
||||
#endif
|
||||
,
|
||||
NULL, /* ssl_version */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue