diff --git a/lib/setopt.c b/lib/setopt.c
index 427cce07af..dd4123cc90 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -1334,11 +1334,33 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option,
return CURLE_UNKNOWN_OPTION;
}
+static bool str_has_crlf(const char *p)
+{
+ while(p && *p) {
+ if(ISNEWLINE(*p))
+ return TRUE;
+ p++;
+ }
+ return FALSE;
+}
+
+static bool list_has_crlf(const struct curl_slist *slist)
+{
+ while(slist) {
+ if(str_has_crlf(slist->data))
+ return TRUE;
+ slist = slist->next;
+ }
+ return FALSE;
+}
+
static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option,
struct curl_slist *slist)
{
CURLcode result = CURLE_OK;
struct UserDefined *s = &data->set;
+ struct curl_slist **slist_set = NULL;
+ struct curl_slist **slist_set2 = NULL;
switch(option) {
#ifndef CURL_DISABLE_PROXY
case CURLOPT_PROXYHEADER:
@@ -1352,7 +1374,7 @@ static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option,
*
* Set this option to NULL to restore the previous behavior.
*/
- s->proxyheaders = slist;
+ slist_set = &s->proxyheaders;
break;
#endif
#ifndef CURL_DISABLE_HTTP
@@ -1360,7 +1382,7 @@ static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option,
/*
* Set a list of aliases for HTTP 200 in response header
*/
- s->http200aliases = slist;
+ slist_set = &s->http200aliases;
break;
#endif
#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH)
@@ -1368,19 +1390,19 @@ static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option,
/*
* List of RAW FTP commands to use after a transfer
*/
- s->postquote = slist;
+ slist_set = &s->postquote;
break;
case CURLOPT_PREQUOTE:
/*
* List of RAW FTP commands to use prior to RETR (Wesley Laxton)
*/
- s->prequote = slist;
+ slist_set = &s->prequote;
break;
case CURLOPT_QUOTE:
/*
* List of RAW FTP commands to use before a transfer
*/
- s->quote = slist;
+ slist_set = &s->quote;
break;
#endif
case CURLOPT_RESOLVE:
@@ -1397,15 +1419,15 @@ static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option,
* This API can remove any entry from the DNS cache, but only entries
* that are not actually in use right now will be pruned immediately.
*/
- s->resolve = slist;
- data->state.resolve = s->resolve;
+ slist_set = &s->resolve;
+ slist_set2 = &data->state.resolve;
break;
#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_MIME)
case CURLOPT_HTTPHEADER:
/*
* Set a list with HTTP headers to use (or replace internals with)
*/
- s->headers = slist;
+ slist_set = &s->headers;
break;
#endif
#ifndef CURL_DISABLE_TELNET
@@ -1413,21 +1435,27 @@ static CURLcode setopt_slist(struct Curl_easy *data, CURLoption option,
/*
* Set a linked list of telnet options
*/
- s->telnet_options = slist;
+ slist_set = &s->telnet_options;
break;
#endif
#ifndef CURL_DISABLE_SMTP
case CURLOPT_MAIL_RCPT:
/* Set the list of mail recipients */
- s->mail_rcpt = slist;
+ slist_set = &s->mail_rcpt;
break;
#endif
case CURLOPT_CONNECT_TO:
- s->connect_to = slist;
+ slist_set = &s->connect_to;
break;
default:
return CURLE_UNKNOWN_OPTION;
}
+ if(list_has_crlf(slist))
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ /* assign once the list has been confirmed to be decent */
+ *slist_set = slist;
+ if(slist_set2)
+ *slist_set2 = slist;
return result;
}
diff --git a/src/config2setopts.c b/src/config2setopts.c
index d7963f6716..c85455b529 100644
--- a/src/config2setopts.c
+++ b/src/config2setopts.c
@@ -948,7 +948,14 @@ static CURLcode credentials_and_headers_setopts(struct OperationConfig *config,
if(config->authtype)
my_setopt_bitmask(curl, CURLOPT_HTTPAUTH, config->authtype);
- my_setopt_slist(curl, CURLOPT_HTTPHEADER, config->headers);
+ result = my_setopt_slist(curl, CURLOPT_HTTPHEADER, config->headers);
+ if(result == CURLE_BAD_FUNCTION_ARGUMENT) {
+ errorf("Illegal CR/LF in --header data");
+ config->synthetic_error = TRUE;
+ return result;
+ }
+ if(setopt_bad(result))
+ return result;
if(proto_http || proto_rtsp) {
MY_SETOPT_STR(curl, CURLOPT_REFERER, config->referer);
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index 76ac5be1bf..e264187654 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -242,7 +242,7 @@ test1979 test1980 test1981 test1982 test1983 test1984 \
\
test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \
test2008 test2009 test2010 test2011 test2012 test2013 test2014 test2015 \
-\
+test2016 test2017 \
test2023 \
test2024 test2025 test2026 test2027 test2028 test2029 test2030 test2031 \
test2032 test2033 test2034 test2035 test2036 test2037 test2038 test2039 \
diff --git a/tests/data/test2016 b/tests/data/test2016
new file mode 100644
index 0000000000..c5b3a09dba
--- /dev/null
+++ b/tests/data/test2016
@@ -0,0 +1,32 @@
+
+
+
+
+HTTP
+
+
+
+# Server-side
+
+
+
+# Client-side
+
+
+http
+
+
+Attempt sending a carriage return in a HTTP header
+
+
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -H 'A: %CR'
+
+
+
+# Verify data after the test has been "shot"
+
+
+43
+
+
+
diff --git a/tests/data/test2017 b/tests/data/test2017
new file mode 100644
index 0000000000..8211d66d8a
--- /dev/null
+++ b/tests/data/test2017
@@ -0,0 +1,34 @@
+
+
+
+
+HTTP
+curl_easy_setopt
+
+
+
+# Server-side
+
+
+
+# Client-side
+
+
+http
+
+
+Pass and reject CR and LF in setopt slist options
+
+
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+
+
+lib%TESTNUMBER
+
+
+
+
+# Verify data after the test has been "shot"
+
+
+
diff --git a/tests/http/test_01_basic.py b/tests/http/test_01_basic.py
index 24e03d19b8..a3faf0b21e 100644
--- a/tests/http/test_01_basic.py
+++ b/tests/http/test_01_basic.py
@@ -236,20 +236,6 @@ class TestBasic:
else:
r.check_exit_code(100) # CURLE_TOO_LARGE
- # http: invalid request headers, GET, issue #16998
- @pytest.mark.parametrize("proto", Env.http_protos())
- def test_01_16_inv_req_get(self, env: Env, httpd, nghttpx, proto):
- curl = CurlClient(env=env)
- url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo'
- r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
- '-H', "a: a\x0ab"
- ])
- # on h1, request is sent, h2/h3 reject
- if proto == 'http/1.1':
- r.check_exit_code(0)
- else:
- r.check_exit_code(43)
-
# http: special handling of TE request header
@pytest.mark.parametrize("te_in, te_out", [
pytest.param('trailers', 'trailers', id='trailers'),
diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc
index 89f6a82546..578ff15e24 100644
--- a/tests/libtest/Makefile.inc
+++ b/tests/libtest/Makefile.inc
@@ -116,6 +116,7 @@ TESTS_C = \
lib1955.c lib1956.c lib1957.c lib1958.c lib1959.c lib1960.c \
lib1964.c lib1965.c lib1967.c lib1970.c \
lib1971.c lib1972.c lib1973.c lib1974.c lib1975.c lib1977.c lib1978.c \
+ lib2017.c \
lib2023.c lib2032.c lib2082.c \
lib2301.c lib2302.c lib2304.c lib2306.c lib2308.c lib2309.c \
lib2402.c lib2404.c lib2405.c \
diff --git a/tests/libtest/lib2017.c b/tests/libtest/lib2017.c
new file mode 100644
index 0000000000..e23c5044de
--- /dev/null
+++ b/tests/libtest/lib2017.c
@@ -0,0 +1,104 @@
+/***************************************************************************
+ * _ _ ____ _
+ * 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 "first.h"
+#include "testtrace.h"
+
+static CURLcode test_lib2017(const char *URL)
+{
+ CURLcode result;
+ CURL *curl;
+ int errors = 0;
+
+ struct curl_slist crlist = {
+ CURL_UNCONST("data and a \x0d in there"), NULL
+ };
+ struct curl_slist lflist = {
+ CURL_UNCONST("data and a \x0a in there"), NULL
+ };
+ struct curl_slist bothlist[] = {
+ { CURL_UNCONST("nothing harmful"), NULL },
+ { CURL_UNCONST("both a \x0a and a \x0d embedded"), NULL}
+ };
+
+ /* all slist options should reject these lists */
+ CURLoption opts[] = {
+ CURLOPT_PROXYHEADER,
+ CURLOPT_HTTP200ALIASES,
+ CURLOPT_POSTQUOTE,
+ CURLOPT_PREQUOTE,
+ CURLOPT_QUOTE,
+ CURLOPT_RESOLVE,
+ CURLOPT_HTTPHEADER,
+ CURLOPT_TELNETOPTIONS,
+ CURLOPT_MAIL_RCPT,
+ CURLOPT_CONNECT_TO,
+ CURLOPT_LASTENTRY /* end of list */
+ };
+
+ int i;
+
+ bothlist[0].next = &bothlist[1];
+
+ if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+ curl_mfprintf(stderr, "curl_global_init() failed\n");
+ return TEST_ERR_MAJOR_BAD;
+ }
+
+ curl = curl_easy_init();
+ if(!curl) {
+ curl_mfprintf(stderr, "curl_easy_init() failed\n");
+ curl_global_cleanup();
+ return TEST_ERR_MAJOR_BAD;
+ }
+
+ easy_setopt(curl, CURLOPT_URL, URL);
+
+ for(i = 0; opts[i] != CURLOPT_LASTENTRY; i++) {
+ CURLcode result1;
+ CURLcode result2;
+ CURLcode result3;
+ CURLoption o = opts[i];
+ result1 = curl_easy_setopt(curl, o, &lflist);
+ if(!result1)
+ curl_mfprintf(stderr, "Option %d unexpectedly OK for LF", (int)o);
+
+ result2 = curl_easy_setopt(curl, opts[i], &crlist);
+ if(!result2)
+ curl_mfprintf(stderr, "Option %d unexpectedly OK for CR", (int)o);
+
+ result3 = curl_easy_setopt(curl, opts[i], bothlist);
+ if(!result3)
+ curl_mfprintf(stderr, "Option %d unexpectedly OK for CR+LF", (int)o);
+
+ if(!result1 || !result2 || !result3)
+ errors++;
+ }
+
+test_cleanup:
+
+ curl_easy_cleanup(curl);
+ curl_global_cleanup();
+
+ return errors ? CURLE_BAD_FUNCTION_ARGUMENT : result;
+}