http: make Content-Length parser more WHATWG

Return error if there is something after the number other than
whitespace and newline.

Allow comma separated numbers and repeated headers as long as the new value is
the same as was set before.

Add test 767 to 771 to verify.

Reported-by: Ignat Loskutov
Fixes #18921
Closes #18925
This commit is contained in:
Daniel Stenberg 2025-10-08 08:33:55 +02:00
parent 0f02744c41
commit 008078fc38
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
7 changed files with 324 additions and 22 deletions

View file

@ -519,6 +519,7 @@ static CURLcode http_perhapsrewind(struct Curl_easy *data,
/* We decided to abort the ongoing transfer */
streamclose(conn, "Mid-auth HTTP and much data left to send");
data->req.size = 0; /* do not download any more than 0 bytes */
data->req.http_bodyless = TRUE;
}
return CURLE_OK;
}
@ -3118,32 +3119,50 @@ static CURLcode http_header_c(struct Curl_easy *data,
struct SingleRequest *k = &data->req;
const char *v;
/* Check for Content-Length: header lines to get size */
/* Check for Content-Length: header lines to get size. Browsers insist we
should accept multiple Content-Length headers and that a comma separated
list also is fine and then we should accept them all as long as they are
the same value. Different values trigger error.
*/
v = (!k->http_bodyless && !data->set.ignorecl) ?
HD_VAL(hd, hdlen, "Content-Length:") : NULL;
if(v) {
curl_off_t contentlength;
int offt = curlx_str_numblanks(&v, &contentlength);
do {
curl_off_t contentlength;
int offt = curlx_str_numblanks(&v, &contentlength);
if(offt == STRE_OK) {
k->size = contentlength;
k->maxdownload = k->size;
}
else if(offt == STRE_OVERFLOW) {
/* out of range */
if(data->set.max_filesize) {
failf(data, "Maximum file size exceeded");
return CURLE_FILESIZE_EXCEEDED;
if(offt == STRE_OVERFLOW) {
/* out of range */
if(data->set.max_filesize) {
failf(data, "Maximum file size exceeded");
return CURLE_FILESIZE_EXCEEDED;
}
streamclose(conn, "overflow content-length");
infof(data, "Overflow Content-Length: value");
return CURLE_OK;
}
streamclose(conn, "overflow content-length");
infof(data, "Overflow Content-Length: value");
}
else {
/* negative or just rubbish - bad HTTP */
failf(data, "Invalid Content-Length: value");
return CURLE_WEIRD_SERVER_REPLY;
}
return CURLE_OK;
else {
if((offt == STRE_OK) &&
((k->size == -1) || /* not set to something before */
(k->size == contentlength))) { /* or the same value */
k->size = contentlength;
curlx_str_passblanks(&v);
/* on a comma, loop and get the next instead */
if(!curlx_str_single(&v, ','))
continue;
if(!curlx_str_newline(&v)) {
k->maxdownload = k->size;
return CURLE_OK;
}
}
/* negative, different value or just rubbish - bad HTTP */
failf(data, "Invalid Content-Length: value");
return CURLE_WEIRD_SERVER_REPLY;
}
} while(1);
}
v = (!k->http_bodyless && data->set.str[STRING_ENCODING]) ?
HD_VAL(hd, hdlen, "Content-Encoding:") : NULL;

View file

@ -109,7 +109,7 @@ test727 test728 test729 test730 test731 test732 test733 test734 test735 \
test736 test737 test738 test739 test740 test741 test742 test743 test744 \
test745 test746 test747 test748 test749 test750 test751 test752 test753 \
test754 test755 test756 test757 test758 test759 test760 test761 test762 \
test763 test764 test765 test766 \
test763 test764 test765 test766 test767 test768 test769 test770 test771 \
\
test780 test781 test782 test783 test784 test785 test786 test787 test788 \
test789 test790 test791 test792 test793 test794 test796 test797 \

58
tests/data/test767 Normal file
View file

@ -0,0 +1,58 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
</keywords>
</info>
#
# Server-side
<reply>
<data crlf="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Accept-Ranges: bytes
Content-Length: 6
Content-Length: 6
Connection: close
Content-Type: text/html
Funny-head: yesyes
-foo-
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP with two Content-Length response header fields same size
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
<limits>
Allocations: 135
Maximum allocated: 136000
</limits>
</verify>
</testcase>

57
tests/data/test768 Normal file
View file

@ -0,0 +1,57 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
</keywords>
</info>
#
# Server-side
<reply>
<data crlf="yes" nocheck="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Accept-Ranges: bytes
Content-Length: 6badness
Content-Length: 44
Connection: close
Content-Type: text/html
Funny-head: yesyes
-foo-
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP with letters after the number in Content-Length
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
<errorcode>
8
</errorcode>
</verify>
</testcase>

53
tests/data/test769 Normal file
View file

@ -0,0 +1,53 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
</keywords>
</info>
#
# Server-side
<reply>
<data crlf="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Accept-Ranges: bytes
Content-Length: 6
Connection: close
Content-Type: text/html
Funny-head: yesyes
-foo-
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP with space after Content-Length number
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
</verify>
</testcase>

58
tests/data/test770 Normal file
View file

@ -0,0 +1,58 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
</keywords>
</info>
#
# Server-side
<reply>
<data crlf="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Accept-Ranges: bytes
Content-Length: 6,06,6
Content-Length: 6, 6
Connection: close
Content-Type: text/html
Funny-head: yesyes
-foo-
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP with Content-Length headers using comma separated list
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
<limits>
Allocations: 135
Maximum allocated: 136000
</limits>
</verify>
</testcase>

57
tests/data/test771 Normal file
View file

@ -0,0 +1,57 @@
<testcase>
<info>
<keywords>
HTTP
HTTP GET
</keywords>
</info>
#
# Server-side
<reply>
<data crlf="yes" nocheck="yes">
HTTP/1.1 200 OK
Date: Tue, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
ETag: "21025-dc7-39462498"
Accept-Ranges: bytes
Content-Length: 44
Content-Length: 6
Connection: close
Content-Type: text/html
Funny-head: yesyes
-foo-
</data>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP with two Content-Length headers using different sizes
</name>
<command>
http://%HOSTIP:%HTTPPORT/%TESTNUMBER
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<protocol crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
User-Agent: curl/%VERSION
Accept: */*
</protocol>
<errorcode>
8
</errorcode>
</verify>
</testcase>