diff --git a/lib/altsvc.c b/lib/altsvc.c
index eb072bb49d..e0834cb663 100644
--- a/lib/altsvc.c
+++ b/lib/altsvc.c
@@ -144,6 +144,20 @@ static struct altsvc *altsvc_create(struct Curl_str *srchost,
srcport, dstport);
}
+/* append the new entry to the list after possibly removing an old entry
+ first */
+static void altsvc_append(struct altsvcinfo *asi, struct altsvc *as)
+{
+ while(Curl_llist_count(&asi->list) >= MAX_ALTSVC_ENTRIES) {
+ /* It's full. Remove the first entry in the list */
+ struct Curl_llist_node *e = Curl_llist_head(&asi->list);
+ struct altsvc *oldas = Curl_node_elem(e);
+ Curl_node_remove(e);
+ altsvc_free(oldas);
+ }
+ Curl_llist_append(&asi->list, as, &as->node);
+}
+
/* only returns SERIOUS errors */
static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line)
{
@@ -180,7 +194,6 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line)
curlx_str_newline(&line))
;
else {
- struct altsvc *as;
char dbuf[MAX_ALTSVC_DATELEN + 1];
time_t expires = 0;
time_t now = time(NULL);
@@ -192,12 +205,12 @@ static CURLcode altsvc_add(struct altsvcinfo *asi, const char *line)
Curl_getdate_capped(dbuf, &expires);
if(now < expires) {
- as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn,
- (size_t)srcport, (size_t)dstport);
+ struct altsvc *as = altsvc_create(&srchost, &dsthost, &srcalpn, &dstalpn,
+ (size_t)srcport, (size_t)dstport);
if(as) {
as->expires = expires;
as->persist = persist ? 1 : 0;
- Curl_llist_append(&asi->list, as, &as->node);
+ altsvc_append(asi, as);
}
else
return CURLE_OUT_OF_MEMORY;
@@ -594,7 +607,7 @@ CURLcode Curl_altsvc_parse(struct Curl_easy *data,
else
as->expires = maxage + secs;
as->persist = persist;
- Curl_llist_append(&asi->list, as, &as->node);
+ altsvc_append(asi, as);
infof(data, "Added alt-svc: %.*s:%d over %s",
(int)curlx_strlen(&dsthost), curlx_str(&dsthost),
dstport, Curl_alpnid2str(dstalpnid));
diff --git a/lib/altsvc.h b/lib/altsvc.h
index d1fed4fe1d..dc1740bce1 100644
--- a/lib/altsvc.h
+++ b/lib/altsvc.h
@@ -28,6 +28,9 @@
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC)
#include "llist.h"
+/* the maximum number of alt-svc entries kept in a single cache */
+#define MAX_ALTSVC_ENTRIES 5000
+
struct althost {
char *host;
uint16_t port;
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 78e3f26585..c3496b133f 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -221,7 +221,7 @@ test1638 test1639 test1640 test1641 test1642 test1643 test1644 \
\
test1650 test1651 test1652 test1653 test1654 test1655 test1656 test1657 \
test1658 test1659 test1660 test1661 test1662 test1663 test1664 test1665 \
-test1666 test1667 test1668 \
+test1666 test1667 test1668 test1669 \
\
test1670 test1671 test1672 test1673 \
\
diff --git a/tests/data/test1669 b/tests/data/test1669
new file mode 100644
index 0000000000..204b45b506
--- /dev/null
+++ b/tests/data/test1669
@@ -0,0 +1,37 @@
+
+
+
+
+unittest
+Alt-Svc
+
+
+
+
+
+unittest
+alt-svc
+
+
+# This date is exactly "20190124 22:34:21" UTC
+
+CURL_TIME=1548369261
+
+
+alt-svc load cache with >5000 entries
+
+
+%LOGDIR/%TESTNUMBER
+
+
+%repeat[5010 x h2 example.com 443 h3 shiny.example.com 8443 "20191231 00:00:00" 0 0%0a]%
+
+
+
+
+
+Allocations: 5100
+Maximum allocated: 600000
+
+
+
diff --git a/tests/unit/Makefile.inc b/tests/unit/Makefile.inc
index 459aee5f84..d8b2ed2d2e 100644
--- a/tests/unit/Makefile.inc
+++ b/tests/unit/Makefile.inc
@@ -41,7 +41,7 @@ TESTS_C = \
unit1636.c \
unit1650.c unit1651.c unit1652.c unit1653.c unit1654.c unit1655.c unit1656.c \
unit1657.c unit1658.c unit1660.c unit1661.c unit1663.c unit1664.c \
- unit1666.c unit1667.c unit1668.c \
+ unit1666.c unit1667.c unit1668.c unit1669.c \
unit1979.c unit1980.c \
unit2600.c unit2601.c unit2602.c unit2603.c unit2604.c unit2605.c \
unit3200.c unit3205.c \
diff --git a/tests/unit/unit1669.c b/tests/unit/unit1669.c
new file mode 100644
index 0000000000..625e004084
--- /dev/null
+++ b/tests/unit/unit1669.c
@@ -0,0 +1,58 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, , 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.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.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "unitcheck.h"
+
+#include "urldata.h"
+#include "altsvc.h"
+
+static CURLcode test_unit1669(const char *arg)
+{
+ UNITTEST_BEGIN_SIMPLE
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_ALTSVC)
+ char outname[256];
+ CURL *curl;
+ CURLcode result;
+ struct altsvcinfo *asi = Curl_altsvc_init();
+ abort_if(!asi, "Curl_altsvc_init");
+ result = Curl_altsvc_load(asi, arg);
+ fail_if(result, "Curl_altsvc_load");
+ if(result)
+ goto fail;
+ curl_global_init(CURL_GLOBAL_ALL);
+ curl = curl_easy_init();
+ fail_if(!curl, "curl_easy_init");
+ if(!curl)
+ goto fail;
+ fail_unless(Curl_llist_count(&asi->list) == MAX_ALTSVC_ENTRIES,
+ "wrong number of entries");
+ curl_msnprintf(outname, sizeof(outname), "%s-out", arg);
+
+ curl_easy_cleanup(curl);
+fail:
+ Curl_altsvc_cleanup(&asi);
+#endif
+
+ UNITTEST_END(curl_global_cleanup())
+}