1
0
mirror of https://github.com/moparisthebest/curl synced 2024-12-23 08:38:49 -05:00

http: fix for tiny "HTTP/0.9" response

Deal with tiny "HTTP/0.9" (header-less) responses by checking the
status-line early, even before a full "HTTP/" is received to allow
detecting 0.9 properly.

Test 1266 and 1267 added to verify.

Fixes #2420
Closes #2872
This commit is contained in:
Daniel Stenberg 2018-08-13 12:12:14 +02:00
parent 233908a55a
commit 8440616f53
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
11 changed files with 164 additions and 44 deletions

View File

@ -801,6 +801,11 @@ endings either CRLF or LF so 't' is appropriate.
#define CURL_SA_FAMILY_T unsigned short
#endif
/* Some convenience macros to get the larger/smaller value out of two given.
We prefix with CURL to prevent name collisions. */
#define CURLMAX(x,y) ((x)>(y)?(x):(y))
#define CURLMIN(x,y) ((x)<(y)?(x):(y))
/* Some versions of the Android SDK is missing the declaration */
#if defined(HAVE_GETPWUID_R) && defined(HAVE_DECL_GETPWUID_R_MISSING)
struct passwd;

View File

@ -2899,17 +2899,32 @@ CURLcode Curl_http(struct connectdata *conn, bool *done)
return result;
}
typedef enum {
STATUS_UNKNOWN, /* not enough data to tell yet */
STATUS_DONE, /* a status line was read */
STATUS_BAD /* not a status line */
} statusline;
/* Check a string for a prefix. Check no more than 'len' bytes */
static bool checkprefixmax(const char *prefix, const char *buffer, size_t len)
{
size_t ch = CURLMIN(strlen(prefix), len);
return curl_strnequal(prefix, buffer, ch);
}
/*
* checkhttpprefix()
*
* Returns TRUE if member of the list matches prefix of string
*/
static bool
static statusline
checkhttpprefix(struct Curl_easy *data,
const char *s)
const char *s, size_t len)
{
struct curl_slist *head = data->set.http200aliases;
bool rc = FALSE;
statusline rc = STATUS_BAD;
statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN;
#ifdef CURL_DOES_CONVERSIONS
/* convert from the network encoding using a scratch area */
char *scratch = strdup(s);
@ -2926,15 +2941,15 @@ checkhttpprefix(struct Curl_easy *data,
#endif /* CURL_DOES_CONVERSIONS */
while(head) {
if(checkprefix(head->data, s)) {
rc = TRUE;
if(checkprefixmax(head->data, s, len)) {
rc = onmatch;
break;
}
head = head->next;
}
if(!rc && (checkprefix("HTTP/", s)))
rc = TRUE;
if((rc != STATUS_DONE) && (checkprefixmax("HTTP/", s, len)))
rc = onmatch;
#ifdef CURL_DOES_CONVERSIONS
free(scratch);
@ -2943,11 +2958,12 @@ checkhttpprefix(struct Curl_easy *data,
}
#ifndef CURL_DISABLE_RTSP
static bool
static statusline
checkrtspprefix(struct Curl_easy *data,
const char *s)
const char *s, size_t len)
{
bool result = FALSE;
statusline result = STATUS_BAD;
statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN;
#ifdef CURL_DOES_CONVERSIONS
/* convert from the network encoding using a scratch area */
@ -2960,30 +2976,31 @@ checkrtspprefix(struct Curl_easy *data,
/* Curl_convert_from_network calls failf if unsuccessful */
result = FALSE; /* can't return CURLE_foobar so return FALSE */
}
else
result = checkprefix("RTSP/", scratch)? TRUE: FALSE;
else if(checkprefixmax("RTSP/", scratch, len))
result = onmatch;
free(scratch);
#else
(void)data; /* unused */
result = checkprefix("RTSP/", s)? TRUE: FALSE;
if(checkprefixmax("RTSP/", s, len))
result = onmatch;
#endif /* CURL_DOES_CONVERSIONS */
return result;
}
#endif /* CURL_DISABLE_RTSP */
static bool
static statusline
checkprotoprefix(struct Curl_easy *data, struct connectdata *conn,
const char *s)
const char *s, size_t len)
{
#ifndef CURL_DISABLE_RTSP
if(conn->handler->protocol & CURLPROTO_RTSP)
return checkrtspprefix(data, s);
return checkrtspprefix(data, s, len);
#else
(void)conn;
#endif /* CURL_DISABLE_RTSP */
return checkhttpprefix(data, s);
return checkhttpprefix(data, s, len);
}
/*
@ -3097,12 +3114,15 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
if(result)
return result;
if(!k->headerline && (k->hbuflen>5)) {
/* make a first check that this looks like a protocol header */
if(!checkprotoprefix(data, conn, data->state.headerbuff)) {
if(!k->headerline) {
/* check if this looks like a protocol header */
statusline st = checkprotoprefix(data, conn, data->state.headerbuff,
k->hbuflen);
if(st == STATUS_BAD) {
/* this is not the beginning of a protocol first header line */
k->header = FALSE;
k->badheader = HEADER_ALLBAD;
streamclose(conn, "bad HTTP: No end-of-message indicator");
break;
}
}
@ -3131,8 +3151,10 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
if(!k->headerline) {
/* the first read header */
if((k->hbuflen>5) &&
!checkprotoprefix(data, conn, data->state.headerbuff)) {
statusline st = checkprotoprefix(data, conn, data->state.headerbuff,
k->hbuflen);
if(st == STATUS_BAD) {
streamclose(conn, "bad HTTP: No end-of-message indicator");
/* this is not the beginning of a protocol first header line */
k->header = FALSE;
if(*nread)
@ -3501,7 +3523,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
compare header line against list of aliases
*/
if(!nc) {
if(checkhttpprefix(data, k->p)) {
if(checkhttpprefix(data, k->p, k->hbuflen) == STATUS_DONE) {
nc = 1;
k->httpcode = 200;
conn->httpversion = 10;

View File

@ -42,7 +42,6 @@
#include "memdebug.h"
#define H2_BUFSIZE 32768
#define MIN(x,y) ((x)<(y)?(x):(y))
#if (NGHTTP2_VERSION_NUM < 0x010000)
#error too old nghttp2 version, upgrade!
@ -667,7 +666,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
Curl_add_buffer(stream->header_recvbuf, "\r\n", 2);
left = stream->header_recvbuf->size_used - stream->nread_header_recvbuf;
ncopy = MIN(stream->len, left);
ncopy = CURLMIN(stream->len, left);
memcpy(&stream->mem[stream->memlen],
stream->header_recvbuf->buffer + stream->nread_header_recvbuf,
@ -753,7 +752,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
if(!stream)
return NGHTTP2_ERR_CALLBACK_FAILURE;
nread = MIN(stream->len, len);
nread = CURLMIN(stream->len, len);
memcpy(&stream->mem[stream->memlen], data, nread);
stream->len -= nread;
@ -1076,7 +1075,7 @@ static ssize_t data_source_read_callback(nghttp2_session *session,
else
return NGHTTP2_ERR_INVALID_ARGUMENT;
nread = MIN(stream->upload_len, length);
nread = CURLMIN(stream->upload_len, length);
if(nread > 0) {
memcpy(buf, stream->upload_mem, nread);
stream->upload_mem += nread;
@ -1534,7 +1533,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
/* If there is body data pending for this stream to return, do that */
size_t left =
stream->header_recvbuf->size_used - stream->nread_header_recvbuf;
size_t ncopy = MIN(len, left);
size_t ncopy = CURLMIN(len, left);
memcpy(mem, stream->header_recvbuf->buffer + stream->nread_header_recvbuf,
ncopy);
stream->nread_header_recvbuf += ncopy;
@ -1570,7 +1569,7 @@ static ssize_t http2_recv(struct connectdata *conn, int sockindex,
}
else if(stream->pausedata) {
DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
nread = MIN(len, stream->pauselen);
nread = CURLMIN(len, stream->pauselen);
memcpy(mem, stream->pausedata, nread);
stream->pausedata += nread;

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2016, Florin Petriuc, <petriuc.florin@gmail.com>
* Copyright (C) 1998 - 2018, Florin Petriuc, <petriuc.florin@gmail.com>
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -123,9 +123,6 @@ static const unsigned long K[64] = {
#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25))
#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3))
#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10))
#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
/* compress 512-bits */
static int sha256_compress(struct sha256_state *md,
unsigned char *buf)
@ -200,7 +197,7 @@ static int SHA256_Update(struct sha256_state *md,
inlen -= block_size;
}
else {
n = MIN(inlen, (block_size - md->curlen));
n = CURLMIN(inlen, (block_size - md->curlen));
memcpy(md->buf + md->curlen, in, n);
md->curlen += n;
in += n;

View File

@ -157,11 +157,6 @@ typedef ssize_t (Curl_recv)(struct connectdata *conn, /* connection data */
#define GOOD_EASY_HANDLE(x) \
((x) && ((x)->magic == CURLEASY_MAGIC_NUMBER))
/* Some convenience macros to get the larger/smaller value out of two given.
We prefix with CURL to prevent name collisions. */
#define CURLMAX(x,y) ((x)>(y)?(x):(y))
#define CURLMIN(x,y) ((x)<(y)?(x):(y))
#ifdef HAVE_GSSAPI
/* Types needed for krb5-ftp connections */
struct krb5buffer {

View File

@ -169,6 +169,7 @@ connection-monitor When used, this will log [DISCONNECT] to the server.input
log when the connection is disconnected.
upgrade when an HTTP upgrade header is found, the server will upgrade
to http2
swsclose instruct server to close connection after response
For TFTP:
writedelay: [secs] delay this amount between reply packets (each packet being

View File

@ -139,7 +139,7 @@ test1228 test1229 test1230 test1231 test1232 test1233 test1234 test1235 \
test1236 test1237 test1238 test1239 test1240 test1241 test1242 test1243 \
test1244 test1245 test1246 test1247 test1248 test1249 test1250 test1251 \
test1252 test1253 test1254 test1255 test1256 test1257 test1258 test1259 \
test1260 test1261 test1262 test1263 test1264 test1265 \
test1260 test1261 test1262 test1263 test1264 test1265 test1266 test1267 \
\
test1280 test1281 test1282 test1283 test1284 test1285 test1286 test1287 \
test1288 test1289 test1290 test1291 test1292 \

46
tests/data/test1266 Normal file
View File

@ -0,0 +1,46 @@
<testcase>
<info>
<keywords>
HTTP/0.9
</keywords>
</info>
#
# Server-side
<reply>
<data>
o
</data>
<servercmd>
swsclose
</servercmd>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP GET with a single-byte HTTP/0.9 response
</name>
<command>
http://%HOSTIP:%HTTPPORT/1266
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
</strip>
<protocol>
GET /1266 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
</verify>
</testcase>

46
tests/data/test1267 Normal file
View File

@ -0,0 +1,46 @@
<testcase>
<info>
<keywords>
HTTP/0.9
</keywords>
</info>
#
# Server-side
<reply>
<data>
HTTPr
</data>
<servercmd>
swsclose
</servercmd>
</reply>
#
# Client-side
<client>
<server>
http
</server>
<name>
HTTP GET with a invalid HTTP/1 response line start
</name>
<command>
http://%HOSTIP:%HTTPPORT/1267
</command>
</client>
#
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
</strip>
<protocol>
GET /1267 HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Accept: */*
</protocol>
</verify>
</testcase>

View File

@ -152,7 +152,7 @@ my $NEGTELNETPORT; # TELNET server port with negotiation
my $srcdir = $ENV{'srcdir'} || '.';
my $CURL="../src/curl".exe_ext(); # what curl executable to run on the tests
my $VCURL=$CURL; # what curl binary to use to verify the servers with
my $VCURL="curl"; # what curl binary to use to verify the servers with
# VCURL is handy to set to the system one when the one you
# just built hangs or crashes and thus prevent verification
my $DBGCURL=$CURL; #"../src/.libs/curl"; # alternative for debugging

View File

@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
* Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@ -124,6 +124,8 @@ struct httprequest {
bool connmon; /* monitor the state of the connection, log disconnects */
bool upgrade; /* test case allows upgrade to http2 */
bool upgrade_request; /* upgrade request found and allowed */
bool close; /* similar to swsclose in response: close connection after
response is sent */
int done_processing;
};
@ -177,6 +179,9 @@ const char *serverlogfile = DEFAULT_LOGFILE;
/* upgrade to http2 */
#define CMD_UPGRADE "upgrade"
/* close connection */
#define CMD_SWSCLOSE "swsclose"
#define END_OF_HEADERS "\r\n\r\n"
enum {
@ -361,7 +366,7 @@ static int parse_servercmd(struct httprequest *req)
int error;
filename = test2file(req->testno);
req->close = FALSE;
stream = fopen(filename, "rb");
if(!stream) {
error = errno;
@ -414,6 +419,10 @@ static int parse_servercmd(struct httprequest *req)
logmsg("enabled upgrade to http2");
req->upgrade = TRUE;
}
else if(!strncmp(CMD_SWSCLOSE, cmd, strlen(CMD_SWSCLOSE))) {
logmsg("swsclose: close this connection after response");
req->close = TRUE;
}
else if(1 == sscanf(cmd, "pipe: %d", &num)) {
logmsg("instructed to allow a pipe size of %d", num);
if(num < 0)
@ -1194,7 +1203,7 @@ static int send_doc(curl_socket_t sock, struct httprequest *req)
/* If the word 'swsclose' is present anywhere in the reply chunk, the
connection will be closed after the data has been sent to the requesting
client... */
if(strstr(buffer, "swsclose") || !count) {
if(strstr(buffer, "swsclose") || !count || req->close) {
persistent = FALSE;
logmsg("connection close instruction \"swsclose\" found in response");
}