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; +}