Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions http-client.carp
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,8 @@ from the streams library.
(set-buf! s (String.suffix &raw consumed))
(set-decoded! s @"")
(set! result (Maybe.Just out)))
(= rc -1)
; end of stream
(do (set-done! s true) (set! result (Maybe.Nothing)) (break))
; rc == 0: need more data
(= rc 0)
; need more data
(match (Connection.read (conn s))
(Result.Success chunk)
(if (= (String.length &chunk) 0)
Expand All @@ -167,7 +165,9 @@ from the streams library.
(StringBuf.append-str &sb (buf s))
(StringBuf.append-str &sb &chunk)
(set-buf! s (StringBuf.to-string &sb))))
(Result.Error _) (do (set-done! s true) (break)))))))
(Result.Error _) (do (set-done! s true) (break)))
; rc < 0: end of stream (-1) or parse error (-2)
(do (set-done! s true) (set! result (Maybe.Nothing)) (break))))))
(StringBuf.delete sb)
result))

Expand Down
26 changes: 24 additions & 2 deletions src/chunked.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>

/* Maximum chunk size: 16 MiB. Prevents malicious servers from triggering
* unbounded allocations via an enormous chunk-size line. */
#define CHUNKED_MAX_CHUNK_SIZE (16 * 1024 * 1024)

/* Decode one chunk from a chunked transfer-encoding buffer.
*
Expand All @@ -13,6 +19,7 @@
* 1 = decoded a chunk (data in *out, length in *consumed)
* 0 = need more data (incomplete chunk in buffer)
* -1 = end of stream (chunk size 0)
* -2 = parse error (invalid hex, overflow, or chunk too large)
*/
int chunked_decode_one(const char *buf, int buf_len, String *out, int *consumed) {
/* Find \r\n to read chunk size */
Expand All @@ -25,8 +32,19 @@ int chunked_decode_one(const char *buf, int buf_len, String *out, int *consumed)
}
if (!crlf) return 0; /* need more data */

/* Parse hex chunk size */
long chunk_size = strtol(buf, NULL, 16);
/* The chunk-size line must start with a hex digit (RFC 7230 §4.1). */
if (buf == crlf || !isxdigit((unsigned char)buf[0])) return -2;

/* Parse hex chunk size, validating that strtol consumed meaningful input
* and stopped at an expected delimiter (\r for end-of-size, or ; for a
* chunk extension per RFC 7230). */
char *endptr = NULL;
long chunk_size = strtol(buf, &endptr, 16);
if (endptr == buf || (endptr != (char *)crlf && *endptr != ';')) return -2;

/* Reject negative values (sign prefix) and sizes above the safety cap. */
if (chunk_size < 0 || chunk_size > CHUNKED_MAX_CHUNK_SIZE) return -2;

if (chunk_size == 0) {
*consumed = (int)(crlf - buf) + 2; /* skip "0\r\n" */
*out = CARP_MALLOC(1);
Expand All @@ -35,7 +53,11 @@ int chunked_decode_one(const char *buf, int buf_len, String *out, int *consumed)
}

int header_len = (int)(crlf - buf) + 2; /* "1a\r\n" */

/* Guard against int overflow in the total-needed calculation. */
if (chunk_size > INT_MAX - header_len - 2) return -2;
int needed = header_len + (int)chunk_size + 2; /* +2 for trailing \r\n */

if (buf_len < needed) return 0; /* need more data */

/* Extract chunk data */
Expand Down
Loading