parent
5b5e2f7318
commit
d485177151
246
lib/altsvc.c
246
lib/altsvc.c
@ -407,26 +407,6 @@ CURLcode Curl_altsvc_save(struct Curl_easy *data,
|
||||
return result;
|
||||
}
|
||||
|
||||
static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
|
||||
{
|
||||
size_t len;
|
||||
const char *protop;
|
||||
const char *p = *ptr;
|
||||
while(ISBLANK(*p))
|
||||
p++;
|
||||
protop = p;
|
||||
while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '='))
|
||||
p++;
|
||||
len = p - protop;
|
||||
*ptr = p;
|
||||
|
||||
if(!len || (len >= buflen))
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
memcpy(alpnbuf, protop, len);
|
||||
alpnbuf[len] = 0;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/* hostcompare() returns true if 'host' matches 'check'. The first host
|
||||
* argument may have a trailing dot present that will be ignored.
|
||||
*/
|
||||
@ -497,144 +477,124 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
|
||||
unsigned short srcport)
|
||||
{
|
||||
const char *p = value;
|
||||
char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
|
||||
struct altsvc *as;
|
||||
unsigned short dstport = srcport; /* the same by default */
|
||||
CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
|
||||
size_t entries = 0;
|
||||
size_t alpnlen = strlen(alpnbuf);
|
||||
size_t srchostlen = strlen(srchost);
|
||||
struct Curl_str alpn;
|
||||
const char *sp;
|
||||
time_t maxage = 24 * 3600; /* default is 24 hours */
|
||||
bool persist = FALSE;
|
||||
#ifdef CURL_DISABLE_VERBOSE_STRINGS
|
||||
(void)data;
|
||||
#endif
|
||||
if(result) {
|
||||
infof(data, "Excessive alt-svc header, ignoring.");
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
DEBUGASSERT(asi);
|
||||
|
||||
/* "clear" is a magic keyword */
|
||||
if(strcasecompare(alpnbuf, "clear")) {
|
||||
/* Flush cached alternatives for this source origin */
|
||||
altsvc_flush(asi, srcalpnid, srchost, srcport);
|
||||
return CURLE_OK;
|
||||
/* initial check for "clear" */
|
||||
if(!Curl_str_until(&p, &alpn, MAX_ALTSVC_LINE, ';') &&
|
||||
!Curl_str_single(&p, ';')) {
|
||||
Curl_str_trimblanks(&alpn);
|
||||
/* "clear" is a magic keyword */
|
||||
if(Curl_str_casecompare(&alpn, "clear")) {
|
||||
/* Flush cached alternatives for this source origin */
|
||||
altsvc_flush(asi, srcalpnid, srchost, srcport);
|
||||
return CURLE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
p = value;
|
||||
|
||||
if(Curl_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))
|
||||
return CURLE_OK; /* strange line */
|
||||
|
||||
Curl_str_trimblanks(&alpn);
|
||||
|
||||
/* Handle the optional 'ma' and 'persist' flags once first, as they need to
|
||||
be known for each alternative service. Unknown flags are skipped. */
|
||||
sp = strchr(p, ';');
|
||||
if(sp) {
|
||||
sp++; /* pass the semicolon */
|
||||
for(;;) {
|
||||
struct Curl_str name;
|
||||
struct Curl_str val;
|
||||
const char *vp;
|
||||
curl_off_t num;
|
||||
bool quoted;
|
||||
/* allow some extra whitespaces around name and value */
|
||||
if(Curl_str_until(&sp, &name, 20, '=') ||
|
||||
Curl_str_single(&sp, '=') ||
|
||||
Curl_str_until(&sp, &val, 80, ';'))
|
||||
break;
|
||||
Curl_str_trimblanks(&name);
|
||||
Curl_str_trimblanks(&val);
|
||||
/* the value might be quoted */
|
||||
vp = Curl_str(&val);
|
||||
quoted = (*vp == '\"');
|
||||
if(quoted)
|
||||
vp++;
|
||||
if(!Curl_str_number(&vp, &num, TIME_T_MAX)) {
|
||||
if(Curl_str_casecompare(&name, "ma"))
|
||||
maxage = (time_t)num;
|
||||
else if(Curl_str_casecompare(&name, "persist") && (num == 1))
|
||||
persist = TRUE;
|
||||
}
|
||||
if(quoted && Curl_str_single(&sp, '\"'))
|
||||
break;
|
||||
if(Curl_str_single(&sp, ';'))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
if(*p == '=') {
|
||||
/* [protocol]="[host][:port]" */
|
||||
enum alpnid dstalpnid = Curl_alpn2alpnid(alpnbuf, alpnlen);
|
||||
p++;
|
||||
if(*p == '\"') {
|
||||
const char *dsthost = "";
|
||||
size_t dstlen = 0; /* destination hostname length */
|
||||
const char *value_ptr;
|
||||
char option[32];
|
||||
curl_off_t num;
|
||||
bool quoted = FALSE;
|
||||
time_t maxage = 24 * 3600; /* default is 24 hours */
|
||||
bool persist = FALSE;
|
||||
bool valid = TRUE;
|
||||
p++;
|
||||
if(*p != ':') {
|
||||
if(!Curl_str_single(&p, '=')) {
|
||||
/* [protocol]="[host][:port], [protocol]="[host][:port]" */
|
||||
enum alpnid dstalpnid =
|
||||
Curl_alpn2alpnid(Curl_str(&alpn), Curl_strlen(&alpn));
|
||||
if(!Curl_str_single(&p, '\"')) {
|
||||
struct Curl_str dsthost;
|
||||
curl_off_t port = 0;
|
||||
if(Curl_str_single(&p, ':')) {
|
||||
/* hostname starts here */
|
||||
const char *hostp = p;
|
||||
if(*p == '[') {
|
||||
/* pass all valid IPv6 letters - does not handle zone id */
|
||||
dstlen = strspn(++p, "0123456789abcdefABCDEF:.");
|
||||
if(p[dstlen] != ']')
|
||||
/* invalid host syntax, bail out */
|
||||
if(Curl_str_single(&p, '[')) {
|
||||
if(Curl_str_until(&p, &dsthost, MAX_ALTSVC_HOSTLEN, ':')) {
|
||||
infof(data, "Bad alt-svc hostname, ignoring.");
|
||||
break;
|
||||
/* we store the IPv6 numerical address *with* brackets */
|
||||
dstlen += 2;
|
||||
p = &p[dstlen-1];
|
||||
}
|
||||
}
|
||||
else {
|
||||
while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
|
||||
p++;
|
||||
dstlen = p - hostp;
|
||||
}
|
||||
if(!dstlen || (dstlen >= MAX_ALTSVC_HOSTLEN)) {
|
||||
infof(data, "Excessive alt-svc hostname, ignoring.");
|
||||
valid = FALSE;
|
||||
}
|
||||
else {
|
||||
dsthost = hostp;
|
||||
/* IPv6 host name */
|
||||
if(Curl_str_until(&p, &dsthost, MAX_IPADR_LEN, ']') ||
|
||||
Curl_str_single(&p, ']')) {
|
||||
infof(data, "Bad alt-svc IPv6 hostname, ignoring.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(Curl_str_single(&p, ':'))
|
||||
break;
|
||||
}
|
||||
else {
|
||||
else
|
||||
/* no destination name, use source host */
|
||||
dsthost = srchost;
|
||||
dstlen = strlen(srchost);
|
||||
}
|
||||
if(*p == ':') {
|
||||
curl_off_t port = 0;
|
||||
p++;
|
||||
if(Curl_str_number(&p, &port, 0xffff) || (*p != '\"')) {
|
||||
infof(data, "Unknown alt-svc port number, ignoring.");
|
||||
valid = FALSE;
|
||||
}
|
||||
else
|
||||
dstport = (unsigned short)port;
|
||||
}
|
||||
if(*p++ != '\"')
|
||||
Curl_str_assign(&dsthost, srchost, strlen(srchost));
|
||||
|
||||
if(Curl_str_number(&p, &port, 0xffff)) {
|
||||
infof(data, "Unknown alt-svc port number, ignoring.");
|
||||
break;
|
||||
/* Handle the optional 'ma' and 'persist' flags. Unknown flags
|
||||
are skipped. */
|
||||
for(;;) {
|
||||
while(ISBLANK(*p))
|
||||
p++;
|
||||
if(*p != ';')
|
||||
break;
|
||||
p++; /* pass the semicolon */
|
||||
if(!*p || ISNEWLINE(*p))
|
||||
break;
|
||||
result = getalnum(&p, option, sizeof(option));
|
||||
if(result) {
|
||||
/* skip option if name is too long */
|
||||
option[0] = '\0';
|
||||
}
|
||||
while(ISBLANK(*p))
|
||||
p++;
|
||||
if(*p != '=')
|
||||
return CURLE_OK;
|
||||
p++;
|
||||
while(ISBLANK(*p))
|
||||
p++;
|
||||
if(!*p)
|
||||
return CURLE_OK;
|
||||
if(*p == '\"') {
|
||||
/* quoted value */
|
||||
p++;
|
||||
quoted = TRUE;
|
||||
}
|
||||
value_ptr = p;
|
||||
if(quoted) {
|
||||
while(*p && *p != '\"')
|
||||
p++;
|
||||
if(!*p++)
|
||||
return CURLE_OK;
|
||||
}
|
||||
else {
|
||||
while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
|
||||
p++;
|
||||
}
|
||||
if(!Curl_str_number(&value_ptr, &num, TIME_T_MAX)) {
|
||||
if(strcasecompare("ma", option))
|
||||
maxage = (time_t)num;
|
||||
else if(strcasecompare("persist", option) && (num == 1))
|
||||
persist = TRUE;
|
||||
}
|
||||
}
|
||||
if(dstalpnid && valid) {
|
||||
|
||||
dstport = (unsigned short)port;
|
||||
|
||||
if(Curl_str_single(&p, '\"'))
|
||||
break;
|
||||
|
||||
if(dstalpnid) {
|
||||
if(!entries++)
|
||||
/* Flush cached alternatives for this source origin, if any - when
|
||||
this is the first entry of the line. */
|
||||
altsvc_flush(asi, srcalpnid, srchost, srcport);
|
||||
|
||||
as = altsvc_createid(srchost, srchostlen,
|
||||
dsthost, dstlen,
|
||||
as = altsvc_createid(srchost, strlen(srchost),
|
||||
Curl_str(&dsthost),
|
||||
Curl_strlen(&dsthost),
|
||||
srcalpnid, dstalpnid,
|
||||
srcport, dstport);
|
||||
if(as) {
|
||||
@ -647,26 +607,28 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
|
||||
as->expires = maxage + secs;
|
||||
as->persist = persist;
|
||||
Curl_llist_append(&asi->list, as, &as->node);
|
||||
infof(data, "Added alt-svc: %s:%d over %s", dsthost, dstport,
|
||||
Curl_alpnid2str(dstalpnid));
|
||||
infof(data, "Added alt-svc: %.*s:%d over %s",
|
||||
(int)Curl_strlen(&dsthost), Curl_str(&dsthost),
|
||||
dstport, Curl_alpnid2str(dstalpnid));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
/* after the double quote there can be a comma if there is 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)
|
||||
break;
|
||||
}
|
||||
if(Curl_str_single(&p, ','))
|
||||
break;
|
||||
|
||||
/* comma means another alternative is present */
|
||||
if(Curl_str_until(&p, &alpn, MAX_ALTSVC_LINE, '='))
|
||||
break;
|
||||
Curl_str_trimblanks(&alpn);
|
||||
}
|
||||
else
|
||||
break;
|
||||
} while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
|
||||
} while(1);
|
||||
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
@ -31,6 +31,12 @@ void Curl_str_init(struct Curl_str *out)
|
||||
out->len = 0;
|
||||
}
|
||||
|
||||
void Curl_str_assign(struct Curl_str *out, const char *str, size_t len)
|
||||
{
|
||||
out->str = str;
|
||||
out->len = len;
|
||||
}
|
||||
|
||||
/* Get a word until the first DELIM or end of string. At least one byte long.
|
||||
return non-zero on error */
|
||||
int Curl_str_until(const char **linep, struct Curl_str *out,
|
||||
|
||||
@ -43,6 +43,7 @@ struct Curl_str {
|
||||
};
|
||||
|
||||
void Curl_str_init(struct Curl_str *out);
|
||||
void Curl_str_assign(struct Curl_str *out, const char *str, size_t len);
|
||||
|
||||
#define Curl_str(x) ((x)->str)
|
||||
#define Curl_strlen(x) ((x)->len)
|
||||
|
||||
@ -71,7 +71,8 @@ UNITTEST_START
|
||||
fail_unless(Curl_llist_count(&asi->list) == 6, "wrong number of entries");
|
||||
|
||||
result = Curl_altsvc_parse(curl, asi,
|
||||
"h2=\"example.com:8080\", h3=\"yesyes.com\"\r\n",
|
||||
"h2=\"example.com:8080\", "
|
||||
"h3=\"yesyes.com:8080\"\r\n",
|
||||
ALPN_h1, "3.example.org", 8080);
|
||||
fail_if(result, "Curl_altsvc_parse(3) failed!");
|
||||
/* that one should make two entries */
|
||||
@ -92,7 +93,8 @@ UNITTEST_START
|
||||
|
||||
result =
|
||||
Curl_altsvc_parse(curl, asi,
|
||||
"h2=\":443\", h3=\":443\"; ma = 120; persist = 1\r\n",
|
||||
"h2=\":443\", h3=\":443\"; "
|
||||
"persist = \"1\"; ma = 120;\r\n",
|
||||
ALPN_h1, "curl.se", 80);
|
||||
fail_if(result, "Curl_altsvc_parse(5) failed!");
|
||||
fail_unless(Curl_llist_count(&asi->list) == 12, "wrong number of entries");
|
||||
@ -103,6 +105,26 @@ UNITTEST_START
|
||||
fail_if(result, "Curl_altsvc_parse(6) failed!");
|
||||
fail_unless(Curl_llist_count(&asi->list) == 10, "wrong number of entries");
|
||||
|
||||
/* only a non-existing alpn */
|
||||
result = Curl_altsvc_parse(curl, asi,
|
||||
"h6=\"example.net:443\"; ma=\"180\";\r\n",
|
||||
ALPN_h2, "5.example.net", 80);
|
||||
|
||||
/* missing quote in alpn host */
|
||||
result = Curl_altsvc_parse(curl, asi,
|
||||
"h2=\"example.net:443,; ma=\"180\";\r\n",
|
||||
ALPN_h2, "6.example.net", 80);
|
||||
|
||||
/* missing port in host name */
|
||||
result = Curl_altsvc_parse(curl, asi,
|
||||
"h2=\"example.net\"; ma=\"180\";\r\n",
|
||||
ALPN_h2, "7.example.net", 80);
|
||||
|
||||
/* illegal port in host name */
|
||||
result = Curl_altsvc_parse(curl, asi,
|
||||
"h2=\"example.net:70000\"; ma=\"180\";\r\n",
|
||||
ALPN_h2, "8.example.net", 80);
|
||||
|
||||
Curl_altsvc_save(curl, asi, outname);
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user