altsvc.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /***************************************************************************
  2. * _ _ ____ _
  3. * Project ___| | | | _ \| |
  4. * / __| | | | |_) | |
  5. * | (__| |_| | _ <| |___
  6. * \___|\___/|_| \_\_____|
  7. *
  8. * Copyright (C) 2019 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
  9. *
  10. * This software is licensed as described in the file COPYING, which
  11. * you should have received as part of this distribution. The terms
  12. * are also available at https://curl.haxx.se/docs/copyright.html.
  13. *
  14. * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  15. * copies of the Software, and permit persons to whom the Software is
  16. * furnished to do so, under the terms of the COPYING file.
  17. *
  18. * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  19. * KIND, either express or implied.
  20. *
  21. ***************************************************************************/
  22. /*
  23. * The Alt-Svc: header is defined in RFC 7838:
  24. * https://tools.ietf.org/html/rfc7838
  25. */
  26. #include "curl_setup.h"
  27. #if !defined(CURL_DISABLE_HTTP) && defined(USE_ALTSVC)
  28. #include <curl/curl.h>
  29. #include "urldata.h"
  30. #include "altsvc.h"
  31. #include "curl_get_line.h"
  32. #include "strcase.h"
  33. #include "parsedate.h"
  34. #include "sendf.h"
  35. #include "warnless.h"
  36. #include "rand.h"
  37. #include "rename.h"
  38. /* The last 3 #include files should be in this order */
  39. #include "curl_printf.h"
  40. #include "curl_memory.h"
  41. #include "memdebug.h"
  42. #define MAX_ALTSVC_LINE 4095
  43. #define MAX_ALTSVC_DATELENSTR "64"
  44. #define MAX_ALTSVC_DATELEN 64
  45. #define MAX_ALTSVC_HOSTLENSTR "512"
  46. #define MAX_ALTSVC_HOSTLEN 512
  47. #define MAX_ALTSVC_ALPNLENSTR "10"
  48. #define MAX_ALTSVC_ALPNLEN 10
  49. #if (defined(USE_QUICHE) || defined(USE_NGTCP2)) && !defined(UNITTESTS)
  50. #define H3VERSION "h3-27"
  51. #else
  52. #define H3VERSION "h3"
  53. #endif
  54. static enum alpnid alpn2alpnid(char *name)
  55. {
  56. if(strcasecompare(name, "h1"))
  57. return ALPN_h1;
  58. if(strcasecompare(name, "h2"))
  59. return ALPN_h2;
  60. if(strcasecompare(name, H3VERSION))
  61. return ALPN_h3;
  62. return ALPN_none; /* unknown, probably rubbish input */
  63. }
  64. /* Given the ALPN ID, return the name */
  65. const char *Curl_alpnid2str(enum alpnid id)
  66. {
  67. switch(id) {
  68. case ALPN_h1:
  69. return "h1";
  70. case ALPN_h2:
  71. return "h2";
  72. case ALPN_h3:
  73. return H3VERSION;
  74. default:
  75. return ""; /* bad */
  76. }
  77. }
  78. static void altsvc_free(struct altsvc *as)
  79. {
  80. free(as->src.host);
  81. free(as->dst.host);
  82. free(as);
  83. }
  84. static struct altsvc *altsvc_createid(const char *srchost,
  85. const char *dsthost,
  86. enum alpnid srcalpnid,
  87. enum alpnid dstalpnid,
  88. unsigned int srcport,
  89. unsigned int dstport)
  90. {
  91. struct altsvc *as = calloc(sizeof(struct altsvc), 1);
  92. if(!as)
  93. return NULL;
  94. as->src.host = strdup(srchost);
  95. if(!as->src.host)
  96. goto error;
  97. as->dst.host = strdup(dsthost);
  98. if(!as->dst.host)
  99. goto error;
  100. as->src.alpnid = srcalpnid;
  101. as->dst.alpnid = dstalpnid;
  102. as->src.port = curlx_ultous(srcport);
  103. as->dst.port = curlx_ultous(dstport);
  104. return as;
  105. error:
  106. altsvc_free(as);
  107. return NULL;
  108. }
  109. static struct altsvc *altsvc_create(char *srchost,
  110. char *dsthost,
  111. char *srcalpn,
  112. char *dstalpn,
  113. unsigned int srcport,
  114. unsigned int dstport)
  115. {
  116. enum alpnid dstalpnid = alpn2alpnid(dstalpn);
  117. enum alpnid srcalpnid = alpn2alpnid(srcalpn);
  118. if(!srcalpnid || !dstalpnid)
  119. return NULL;
  120. return altsvc_createid(srchost, dsthost, srcalpnid, dstalpnid,
  121. srcport, dstport);
  122. }
  123. /* only returns SERIOUS errors */
  124. static CURLcode altsvc_add(struct altsvcinfo *asi, char *line)
  125. {
  126. /* Example line:
  127. h2 example.com 443 h3 shiny.example.com 8443 "20191231 10:00:00" 1
  128. */
  129. char srchost[MAX_ALTSVC_HOSTLEN + 1];
  130. char dsthost[MAX_ALTSVC_HOSTLEN + 1];
  131. char srcalpn[MAX_ALTSVC_ALPNLEN + 1];
  132. char dstalpn[MAX_ALTSVC_ALPNLEN + 1];
  133. char date[MAX_ALTSVC_DATELEN + 1];
  134. unsigned int srcport;
  135. unsigned int dstport;
  136. unsigned int prio;
  137. unsigned int persist;
  138. int rc;
  139. rc = sscanf(line,
  140. "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
  141. "%" MAX_ALTSVC_ALPNLENSTR "s %" MAX_ALTSVC_HOSTLENSTR "s %u "
  142. "\"%" MAX_ALTSVC_DATELENSTR "[^\"]\" %u %u",
  143. srcalpn, srchost, &srcport,
  144. dstalpn, dsthost, &dstport,
  145. date, &persist, &prio);
  146. if(9 == rc) {
  147. struct altsvc *as;
  148. time_t expires = Curl_getdate_capped(date);
  149. as = altsvc_create(srchost, dsthost, srcalpn, dstalpn, srcport, dstport);
  150. if(as) {
  151. as->expires = expires;
  152. as->prio = prio;
  153. as->persist = persist ? 1 : 0;
  154. Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
  155. asi->num++; /* one more entry */
  156. }
  157. }
  158. return CURLE_OK;
  159. }
  160. /*
  161. * Load alt-svc entries from the given file. The text based line-oriented file
  162. * format is documented here:
  163. * https://github.com/curl/curl/wiki/QUIC-implementation
  164. *
  165. * This function only returns error on major problems that prevents alt-svc
  166. * handling to work completely. It will ignore individual syntactical errors
  167. * etc.
  168. */
  169. static CURLcode altsvc_load(struct altsvcinfo *asi, const char *file)
  170. {
  171. CURLcode result = CURLE_OK;
  172. char *line = NULL;
  173. FILE *fp;
  174. /* we need a private copy of the file name so that the altsvc cache file
  175. name survives an easy handle reset */
  176. free(asi->filename);
  177. asi->filename = strdup(file);
  178. if(!asi->filename)
  179. return CURLE_OUT_OF_MEMORY;
  180. fp = fopen(file, FOPEN_READTEXT);
  181. if(fp) {
  182. line = malloc(MAX_ALTSVC_LINE);
  183. if(!line)
  184. goto fail;
  185. while(Curl_get_line(line, MAX_ALTSVC_LINE, fp)) {
  186. char *lineptr = line;
  187. while(*lineptr && ISBLANK(*lineptr))
  188. lineptr++;
  189. if(*lineptr == '#')
  190. /* skip commented lines */
  191. continue;
  192. altsvc_add(asi, lineptr);
  193. }
  194. free(line); /* free the line buffer */
  195. fclose(fp);
  196. }
  197. return result;
  198. fail:
  199. Curl_safefree(asi->filename);
  200. free(line);
  201. fclose(fp);
  202. return CURLE_OUT_OF_MEMORY;
  203. }
  204. /*
  205. * Write this single altsvc entry to a single output line
  206. */
  207. static CURLcode altsvc_out(struct altsvc *as, FILE *fp)
  208. {
  209. struct tm stamp;
  210. CURLcode result = Curl_gmtime(as->expires, &stamp);
  211. if(result)
  212. return result;
  213. fprintf(fp,
  214. "%s %s %u "
  215. "%s %s %u "
  216. "\"%d%02d%02d "
  217. "%02d:%02d:%02d\" "
  218. "%u %d\n",
  219. Curl_alpnid2str(as->src.alpnid), as->src.host, as->src.port,
  220. Curl_alpnid2str(as->dst.alpnid), as->dst.host, as->dst.port,
  221. stamp.tm_year + 1900, stamp.tm_mon + 1, stamp.tm_mday,
  222. stamp.tm_hour, stamp.tm_min, stamp.tm_sec,
  223. as->persist, as->prio);
  224. return CURLE_OK;
  225. }
  226. /* ---- library-wide functions below ---- */
  227. /*
  228. * Curl_altsvc_init() creates a new altsvc cache.
  229. * It returns the new instance or NULL if something goes wrong.
  230. */
  231. struct altsvcinfo *Curl_altsvc_init(void)
  232. {
  233. struct altsvcinfo *asi = calloc(sizeof(struct altsvcinfo), 1);
  234. if(!asi)
  235. return NULL;
  236. Curl_llist_init(&asi->list, NULL);
  237. /* set default behavior */
  238. asi->flags = CURLALTSVC_H1
  239. #ifdef USE_NGHTTP2
  240. | CURLALTSVC_H2
  241. #endif
  242. #ifdef ENABLE_QUIC
  243. | CURLALTSVC_H3
  244. #endif
  245. ;
  246. return asi;
  247. }
  248. /*
  249. * Curl_altsvc_load() loads alt-svc from file.
  250. */
  251. CURLcode Curl_altsvc_load(struct altsvcinfo *asi, const char *file)
  252. {
  253. CURLcode result;
  254. DEBUGASSERT(asi);
  255. result = altsvc_load(asi, file);
  256. return result;
  257. }
  258. /*
  259. * Curl_altsvc_ctrl() passes on the external bitmask.
  260. */
  261. CURLcode Curl_altsvc_ctrl(struct altsvcinfo *asi, const long ctrl)
  262. {
  263. DEBUGASSERT(asi);
  264. if(!ctrl)
  265. /* unexpected */
  266. return CURLE_BAD_FUNCTION_ARGUMENT;
  267. asi->flags = ctrl;
  268. return CURLE_OK;
  269. }
  270. /*
  271. * Curl_altsvc_cleanup() frees an altsvc cache instance and all associated
  272. * resources.
  273. */
  274. void Curl_altsvc_cleanup(struct altsvcinfo *altsvc)
  275. {
  276. struct curl_llist_element *e;
  277. struct curl_llist_element *n;
  278. if(altsvc) {
  279. for(e = altsvc->list.head; e; e = n) {
  280. struct altsvc *as = e->ptr;
  281. n = e->next;
  282. altsvc_free(as);
  283. }
  284. free(altsvc->filename);
  285. free(altsvc);
  286. }
  287. }
  288. /*
  289. * Curl_altsvc_save() writes the altsvc cache to a file.
  290. */
  291. CURLcode Curl_altsvc_save(struct Curl_easy *data,
  292. struct altsvcinfo *altsvc, const char *file)
  293. {
  294. struct curl_llist_element *e;
  295. struct curl_llist_element *n;
  296. CURLcode result = CURLE_OK;
  297. FILE *out;
  298. char *tempstore;
  299. unsigned char randsuffix[9];
  300. if(!altsvc)
  301. /* no cache activated */
  302. return CURLE_OK;
  303. /* if not new name is given, use the one we stored from the load */
  304. if(!file && altsvc->filename)
  305. file = altsvc->filename;
  306. if((altsvc->flags & CURLALTSVC_READONLYFILE) || !file || !file[0])
  307. /* marked as read-only, no file or zero length file name */
  308. return CURLE_OK;
  309. if(Curl_rand_hex(data, randsuffix, sizeof(randsuffix)))
  310. return CURLE_FAILED_INIT;
  311. tempstore = aprintf("%s.%s.tmp", file, randsuffix);
  312. if(!tempstore)
  313. return CURLE_OUT_OF_MEMORY;
  314. out = fopen(tempstore, FOPEN_WRITETEXT);
  315. if(!out)
  316. result = CURLE_WRITE_ERROR;
  317. else {
  318. fputs("# Your alt-svc cache. https://curl.haxx.se/docs/alt-svc.html\n"
  319. "# This file was generated by libcurl! Edit at your own risk.\n",
  320. out);
  321. for(e = altsvc->list.head; e; e = n) {
  322. struct altsvc *as = e->ptr;
  323. n = e->next;
  324. result = altsvc_out(as, out);
  325. if(result)
  326. break;
  327. }
  328. fclose(out);
  329. if(!result && Curl_rename(tempstore, file))
  330. result = CURLE_WRITE_ERROR;
  331. if(result)
  332. unlink(tempstore);
  333. }
  334. free(tempstore);
  335. return result;
  336. }
  337. static CURLcode getalnum(const char **ptr, char *alpnbuf, size_t buflen)
  338. {
  339. size_t len;
  340. const char *protop;
  341. const char *p = *ptr;
  342. while(*p && ISBLANK(*p))
  343. p++;
  344. protop = p;
  345. while(*p && !ISBLANK(*p) && (*p != ';') && (*p != '='))
  346. p++;
  347. len = p - protop;
  348. *ptr = p;
  349. if(!len || (len >= buflen))
  350. return CURLE_BAD_FUNCTION_ARGUMENT;
  351. memcpy(alpnbuf, protop, len);
  352. alpnbuf[len] = 0;
  353. return CURLE_OK;
  354. }
  355. /* altsvc_flush() removes all alternatives for this source origin from the
  356. list */
  357. static void altsvc_flush(struct altsvcinfo *asi, enum alpnid srcalpnid,
  358. const char *srchost, unsigned short srcport)
  359. {
  360. struct curl_llist_element *e;
  361. struct curl_llist_element *n;
  362. for(e = asi->list.head; e; e = n) {
  363. struct altsvc *as = e->ptr;
  364. n = e->next;
  365. if((srcalpnid == as->src.alpnid) &&
  366. (srcport == as->src.port) &&
  367. strcasecompare(srchost, as->src.host)) {
  368. Curl_llist_remove(&asi->list, e, NULL);
  369. altsvc_free(as);
  370. asi->num--;
  371. }
  372. }
  373. }
  374. #ifdef DEBUGBUILD
  375. /* to play well with debug builds, we can *set* a fixed time this will
  376. return */
  377. static time_t debugtime(void *unused)
  378. {
  379. char *timestr = getenv("CURL_TIME");
  380. (void)unused;
  381. if(timestr) {
  382. unsigned long val = strtol(timestr, NULL, 10);
  383. return (time_t)val;
  384. }
  385. return time(NULL);
  386. }
  387. #define time(x) debugtime(x)
  388. #endif
  389. /*
  390. * Curl_altsvc_parse() takes an incoming alt-svc response header and stores
  391. * the data correctly in the cache.
  392. *
  393. * 'value' points to the header *value*. That's contents to the right of the
  394. * header name.
  395. *
  396. * Currently this function rejects invalid data without returning an error.
  397. * Invalid host name, port number will result in the specific alternative
  398. * being rejected. Unknown protocols are skipped.
  399. */
  400. CURLcode Curl_altsvc_parse(struct Curl_easy *data,
  401. struct altsvcinfo *asi, const char *value,
  402. enum alpnid srcalpnid, const char *srchost,
  403. unsigned short srcport)
  404. {
  405. const char *p = value;
  406. size_t len;
  407. enum alpnid dstalpnid = srcalpnid; /* the same by default */
  408. char namebuf[MAX_ALTSVC_HOSTLEN] = "";
  409. char alpnbuf[MAX_ALTSVC_ALPNLEN] = "";
  410. struct altsvc *as;
  411. unsigned short dstport = srcport; /* the same by default */
  412. CURLcode result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
  413. if(result) {
  414. infof(data, "Excessive alt-svc header, ignoring...\n");
  415. return CURLE_OK;
  416. }
  417. DEBUGASSERT(asi);
  418. /* Flush all cached alternatives for this source origin, if any */
  419. altsvc_flush(asi, srcalpnid, srchost, srcport);
  420. /* "clear" is a magic keyword */
  421. if(strcasecompare(alpnbuf, "clear")) {
  422. return CURLE_OK;
  423. }
  424. do {
  425. if(*p == '=') {
  426. /* [protocol]="[host][:port]" */
  427. dstalpnid = alpn2alpnid(alpnbuf);
  428. p++;
  429. if(*p == '\"') {
  430. const char *dsthost;
  431. const char *value_ptr;
  432. char option[32];
  433. unsigned long num;
  434. char *end_ptr;
  435. bool quoted = FALSE;
  436. time_t maxage = 24 * 3600; /* default is 24 hours */
  437. bool persist = FALSE;
  438. p++;
  439. if(*p != ':') {
  440. /* host name starts here */
  441. const char *hostp = p;
  442. while(*p && (ISALNUM(*p) || (*p == '.') || (*p == '-')))
  443. p++;
  444. len = p - hostp;
  445. if(!len || (len >= MAX_ALTSVC_HOSTLEN)) {
  446. infof(data, "Excessive alt-svc host name, ignoring...\n");
  447. dstalpnid = ALPN_none;
  448. }
  449. else {
  450. memcpy(namebuf, hostp, len);
  451. namebuf[len] = 0;
  452. dsthost = namebuf;
  453. }
  454. }
  455. else {
  456. /* no destination name, use source host */
  457. dsthost = srchost;
  458. }
  459. if(*p == ':') {
  460. /* a port number */
  461. unsigned long port = strtoul(++p, &end_ptr, 10);
  462. if(port > USHRT_MAX || end_ptr == p || *end_ptr != '\"') {
  463. infof(data, "Unknown alt-svc port number, ignoring...\n");
  464. dstalpnid = ALPN_none;
  465. }
  466. p = end_ptr;
  467. dstport = curlx_ultous(port);
  468. }
  469. if(*p++ != '\"')
  470. break;
  471. /* Handle the optional 'ma' and 'persist' flags. Unknown flags
  472. are skipped. */
  473. for(;;) {
  474. while(*p && ISBLANK(*p) && *p != ';' && *p != ',')
  475. p++;
  476. if(!*p || *p == ',')
  477. break;
  478. p++; /* pass the semicolon */
  479. if(!*p)
  480. break;
  481. result = getalnum(&p, option, sizeof(option));
  482. if(result) {
  483. /* skip option if name is too long */
  484. option[0] = '\0';
  485. }
  486. while(*p && ISBLANK(*p))
  487. p++;
  488. if(*p != '=')
  489. return CURLE_OK;
  490. p++;
  491. while(*p && ISBLANK(*p))
  492. p++;
  493. if(!*p)
  494. return CURLE_OK;
  495. if(*p == '\"') {
  496. /* quoted value */
  497. p++;
  498. quoted = TRUE;
  499. }
  500. value_ptr = p;
  501. if(quoted) {
  502. while(*p && *p != '\"')
  503. p++;
  504. if(!*p++)
  505. return CURLE_OK;
  506. }
  507. else {
  508. while(*p && !ISBLANK(*p) && *p!= ';' && *p != ',')
  509. p++;
  510. }
  511. num = strtoul(value_ptr, &end_ptr, 10);
  512. if((end_ptr != value_ptr) && (num < ULONG_MAX)) {
  513. if(strcasecompare("ma", option))
  514. maxage = num;
  515. else if(strcasecompare("persist", option) && (num == 1))
  516. persist = TRUE;
  517. }
  518. }
  519. if(dstalpnid) {
  520. as = altsvc_createid(srchost, dsthost,
  521. srcalpnid, dstalpnid,
  522. srcport, dstport);
  523. if(as) {
  524. /* The expires time also needs to take the Age: value (if any) into
  525. account. [See RFC 7838 section 3.1] */
  526. as->expires = maxage + time(NULL);
  527. as->persist = persist;
  528. Curl_llist_insert_next(&asi->list, asi->list.tail, as, &as->node);
  529. asi->num++; /* one more entry */
  530. infof(data, "Added alt-svc: %s:%d over %s\n", dsthost, dstport,
  531. Curl_alpnid2str(dstalpnid));
  532. }
  533. }
  534. else {
  535. infof(data, "Unknown alt-svc protocol \"%s\", skipping...\n",
  536. alpnbuf);
  537. }
  538. }
  539. else
  540. break;
  541. /* after the double quote there can be a comma if there's another
  542. string or a semicolon if no more */
  543. if(*p == ',') {
  544. /* comma means another alternative is presented */
  545. p++;
  546. result = getalnum(&p, alpnbuf, sizeof(alpnbuf));
  547. if(result)
  548. break;
  549. }
  550. }
  551. else
  552. break;
  553. } while(*p && (*p != ';') && (*p != '\n') && (*p != '\r'));
  554. return CURLE_OK;
  555. }
  556. /*
  557. * Return TRUE on a match
  558. */
  559. bool Curl_altsvc_lookup(struct altsvcinfo *asi,
  560. enum alpnid srcalpnid, const char *srchost,
  561. int srcport,
  562. struct altsvc **dstentry,
  563. const int versions) /* one or more bits */
  564. {
  565. struct curl_llist_element *e;
  566. struct curl_llist_element *n;
  567. time_t now = time(NULL);
  568. DEBUGASSERT(asi);
  569. DEBUGASSERT(srchost);
  570. DEBUGASSERT(dstentry);
  571. for(e = asi->list.head; e; e = n) {
  572. struct altsvc *as = e->ptr;
  573. n = e->next;
  574. if(as->expires < now) {
  575. /* an expired entry, remove */
  576. Curl_llist_remove(&asi->list, e, NULL);
  577. altsvc_free(as);
  578. continue;
  579. }
  580. if((as->src.alpnid == srcalpnid) &&
  581. strcasecompare(as->src.host, srchost) &&
  582. (as->src.port == srcport) &&
  583. (versions & as->dst.alpnid)) {
  584. /* match */
  585. *dstentry = as;
  586. return TRUE;
  587. }
  588. }
  589. return FALSE;
  590. }
  591. #endif /* CURL_DISABLE_HTTP || USE_ALTSVC */