Merge pull request #2205 from cesanta/arbitrary_content-length

improve negative Content-length handling
This commit is contained in:
Sergey Lyubka 2023-05-26 17:16:08 +01:00 committed by GitHub
commit 1ccc9aa72e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 102 deletions

View File

@ -1243,6 +1243,22 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
bool mg_to_size_t(struct mg_str str, size_t *val);
bool mg_to_size_t(struct mg_str str, size_t *val) {
uint64_t result = 0, max = 1844674407370955160 /* (UINT64_MAX-9)/10 */;
size_t i = 0;
while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
if (i < str.len && str.ptr[i] == '-') return false;
while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
if (result > max) return false;
result *= 10;
result += (unsigned) (str.ptr[i] - '0');
i++;
}
*val = (size_t) result;
return true;
}
// Chunk deletion marker is the MSB in the "processed" counter
#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
@ -1382,7 +1398,9 @@ int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
return i >= src_len && j < dst_len ? (int) j : -1;
}
static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c >= ' '; }
static bool isok(uint8_t c) {
return c == '\n' || c == '\r' || c >= ' ';
}
int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
size_t i;
@ -1463,9 +1481,7 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
mg_http_parse_headers(s, end, hm->headers,
sizeof(hm->headers) / sizeof(hm->headers[0]));
if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
int64_t content_len = mg_to64(*cl);
if(content_len < 0) return -1;
hm->body.len = (size_t) content_len;
if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
hm->message.len = (size_t) req_len + hm->body.len;
}
@ -1662,18 +1678,18 @@ static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
return mg_str("text/plain; charset=utf-8");
}
static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
static int getrange(struct mg_str *s, size_t *a, size_t *b) {
size_t i, numparsed = 0;
// MG_INFO(("%.*s", (int) s->len, s->ptr));
for (i = 0; i + 6 < s->len; i++) {
if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
*a = mg_to64(p);
if (!mg_to_size_t(p, a)) return 0;
// MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--;
if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
*b = mg_to64(p);
if (!mg_to_size_t(p, b)) return 0;
if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
// MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
break;
@ -1725,12 +1741,12 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
} else {
int n, status = 200;
char range[100];
int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
size_t r1 = 0, r2 = 0, cl = size;
// Handle Range header
struct mg_str *rh = mg_http_get_header(hm, "Range");
range[0] = '\0';
if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) {
if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
// If range is specified like "400-", set second limit to content len
if (n == 1) r2 = cl - 1;
if (r1 > r2 || r2 >= cl) {
@ -1742,9 +1758,9 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
status = 206;
cl = r2 - r1 + 1;
mg_snprintf(range, sizeof(range),
"Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
(int64_t) size);
fs->sk(fd->fd, (size_t) r1);
"Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
(uint64_t) (r1 + cl - 1), (uint64_t) size);
fs->sk(fd->fd, r1);
}
}
mg_printf(c,
@ -1754,8 +1770,8 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
"Content-Length: %llu\r\n"
"%s%s%s\r\n",
status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
opts->extra_headers ? opts->extra_headers : "");
etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
range, opts->extra_headers ? opts->extra_headers : "");
if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
c->is_draining = 1;
c->is_resp = 0;
@ -1766,7 +1782,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
sizeof(size_t) * sizeof(size_t)];
c->pfn = static_cb;
c->pfn_data = fd;
*clp = (size_t) cl;
*clp = cl;
}
}
}
@ -5306,32 +5322,6 @@ void mg_unhex(const char *buf, size_t len, unsigned char *to) {
}
}
uint64_t mg_tou64(struct mg_str str) {
uint64_t result = 0;
size_t i = 0;
while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
result *= 10;
result += (unsigned) (str.ptr[i] - '0');
i++;
}
return result;
}
int64_t mg_to64(struct mg_str str) {
int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */;
size_t i = 0;
while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
if (result > max) return 0;
result *= 10;
result += (str.ptr[i] - '0');
i++;
}
return result * neg;
}
char *mg_remove_double_dots(char *s) {
char *saved = s, *p = s;
while (*s != '\0') {

View File

@ -829,8 +829,6 @@ char *mg_hex(const void *buf, size_t len, char *dst);
void mg_unhex(const char *buf, size_t len, unsigned char *to);
unsigned long mg_unhexn(const char *s, size_t len);
int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
int64_t mg_to64(struct mg_str str);
uint64_t mg_tou64(struct mg_str str);
char *mg_remove_double_dots(char *s);

View File

@ -1,7 +1,7 @@
#include "http.h"
#include "arch.h"
#include "base64.h"
#include "fmt.h"
#include "http.h"
#include "json.h"
#include "log.h"
#include "net.h"
@ -11,6 +11,22 @@
#include "version.h"
#include "ws.h"
bool mg_to_size_t(struct mg_str str, size_t *val);
bool mg_to_size_t(struct mg_str str, size_t *val) {
uint64_t result = 0, max = 1844674407370955160 /* (UINT64_MAX-9)/10 */;
size_t i = 0;
while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
if (i < str.len && str.ptr[i] == '-') return false;
while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
if (result > max) return false;
result *= 10;
result += (unsigned) (str.ptr[i] - '0');
i++;
}
*val = (size_t) result;
return true;
}
// Chunk deletion marker is the MSB in the "processed" counter
#define MG_DMARK ((size_t) 1 << (sizeof(size_t) * 8 - 1))
@ -150,7 +166,9 @@ int mg_url_decode(const char *src, size_t src_len, char *dst, size_t dst_len,
return i >= src_len && j < dst_len ? (int) j : -1;
}
static bool isok(uint8_t c) { return c == '\n' || c == '\r' || c >= ' '; }
static bool isok(uint8_t c) {
return c == '\n' || c == '\r' || c >= ' ';
}
int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
size_t i;
@ -231,9 +249,7 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
mg_http_parse_headers(s, end, hm->headers,
sizeof(hm->headers) / sizeof(hm->headers[0]));
if ((cl = mg_http_get_header(hm, "Content-Length")) != NULL) {
int64_t content_len = mg_to64(*cl);
if(content_len < 0) return -1;
hm->body.len = (size_t) content_len;
if (mg_to_size_t(*cl, &hm->body.len) == false) return -1;
hm->message.len = (size_t) req_len + hm->body.len;
}
@ -430,18 +446,18 @@ static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
return mg_str("text/plain; charset=utf-8");
}
static int getrange(struct mg_str *s, int64_t *a, int64_t *b) {
static int getrange(struct mg_str *s, size_t *a, size_t *b) {
size_t i, numparsed = 0;
// MG_INFO(("%.*s", (int) s->len, s->ptr));
for (i = 0; i + 6 < s->len; i++) {
if (memcmp(&s->ptr[i], "bytes=", 6) == 0) {
struct mg_str p = mg_str_n(s->ptr + i + 6, s->len - i - 6);
if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
*a = mg_to64(p);
if (!mg_to_size_t(p, a)) return 0;
// MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
while (p.len && p.ptr[0] >= '0' && p.ptr[0] <= '9') p.ptr++, p.len--;
if (p.len && p.ptr[0] == '-') p.ptr++, p.len--;
*b = mg_to64(p);
if (!mg_to_size_t(p, b)) return 0;
if (p.len > 0 && p.ptr[0] >= '0' && p.ptr[0] <= '9') numparsed++;
// MG_INFO(("PPP [%.*s] %d", (int) p.len, p.ptr, numparsed));
break;
@ -493,12 +509,12 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
} else {
int n, status = 200;
char range[100];
int64_t r1 = 0, r2 = 0, cl = (int64_t) size;
size_t r1 = 0, r2 = 0, cl = size;
// Handle Range header
struct mg_str *rh = mg_http_get_header(hm, "Range");
range[0] = '\0';
if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0 && r1 >= 0 && r2 >= 0) {
if (rh != NULL && (n = getrange(rh, &r1, &r2)) > 0) {
// If range is specified like "400-", set second limit to content len
if (n == 1) r2 = cl - 1;
if (r1 > r2 || r2 >= cl) {
@ -510,9 +526,9 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
status = 206;
cl = r2 - r1 + 1;
mg_snprintf(range, sizeof(range),
"Content-Range: bytes %lld-%lld/%lld\r\n", r1, r1 + cl - 1,
(int64_t) size);
fs->sk(fd->fd, (size_t) r1);
"Content-Range: bytes %llu-%llu/%llu\r\n", (uint64_t) r1,
(uint64_t) (r1 + cl - 1), (uint64_t) size);
fs->sk(fd->fd, r1);
}
}
mg_printf(c,
@ -522,8 +538,8 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
"Content-Length: %llu\r\n"
"%s%s%s\r\n",
status, mg_http_status_code_str(status), (int) mime.len, mime.ptr,
etag, cl, gzip ? "Content-Encoding: gzip\r\n" : "", range,
opts->extra_headers ? opts->extra_headers : "");
etag, (uint64_t) cl, gzip ? "Content-Encoding: gzip\r\n" : "",
range, opts->extra_headers ? opts->extra_headers : "");
if (mg_vcasecmp(&hm->method, "HEAD") == 0) {
c->is_draining = 1;
c->is_resp = 0;
@ -534,7 +550,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm,
sizeof(size_t) * sizeof(size_t)];
c->pfn = static_cb;
c->pfn_data = fd;
*clp = (size_t) cl;
*clp = cl;
}
}
}

View File

@ -187,32 +187,6 @@ void mg_unhex(const char *buf, size_t len, unsigned char *to) {
}
}
uint64_t mg_tou64(struct mg_str str) {
uint64_t result = 0;
size_t i = 0;
while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
result *= 10;
result += (unsigned) (str.ptr[i] - '0');
i++;
}
return result;
}
int64_t mg_to64(struct mg_str str) {
int64_t result = 0, neg = 1, max = 922337203685477570 /* INT64_MAX/10-10 */;
size_t i = 0;
while (i < str.len && (str.ptr[i] == ' ' || str.ptr[i] == '\t')) i++;
if (i < str.len && str.ptr[i] == '-') neg = -1, i++;
while (i < str.len && str.ptr[i] >= '0' && str.ptr[i] <= '9') {
if (result > max) return 0;
result *= 10;
result += (str.ptr[i] - '0');
i++;
}
return result * neg;
}
char *mg_remove_double_dots(char *s) {
char *saved = s, *p = s;
while (*s != '\0') {

View File

@ -35,6 +35,4 @@ char *mg_hex(const void *buf, size_t len, char *dst);
void mg_unhex(const char *buf, size_t len, unsigned char *to);
unsigned long mg_unhexn(const char *s, size_t len);
int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip);
int64_t mg_to64(struct mg_str str);
uint64_t mg_tou64(struct mg_str str);
char *mg_remove_double_dots(char *s);

View File

@ -790,10 +790,18 @@ static void test_http_server(void) {
ASSERT(fetch(&mgr, buf, url, "GET /..ddot HTTP/1.0\n\n") == 301);
ASSERT(fetch(&mgr, buf, url, "GET /..ddot/ HTTP/1.0\n\n") == 200);
ASSERT(cmpbody(buf, "hi\n") == 0);
ASSERT(fetch(&mgr, buf, url, "GET /a.txt HTTP/1.0\n"
"Content-Length: -123\n\n") == 0);
ASSERT(fetch(&mgr, buf, url, "POST /a.txt HTTP/1.0\n"
ASSERT(fetch(&mgr, buf, url,
"GET /a.txt HTTP/1.0\n"
"Content-Length: -123\n\n") == 0);
ASSERT(fetch(&mgr, buf, url,
"POST /a.txt HTTP/1.0\n"
"Content-Length: -123\n\n") == 0);
ASSERT(fetch(&mgr, buf, url,
"GET /a.txt HTTP/1.0\n"
"Content-Length: 19000000000000000000\n\n") == 0);
ASSERT(fetch(&mgr, buf, url,
"POST /a.txt HTTP/1.0\n"
"Content-Length: 19000000000000000000\n\n") == 0);
{
extern char *mg_http_etag(char *, size_t, size_t, time_t);
@ -1405,7 +1413,9 @@ static void test_http_range(void) {
ASSERT(mgr.conns == NULL);
}
static void f1(void *arg) { (*(int *) arg)++; }
static void f1(void *arg) {
(*(int *) arg)++;
}
static void test_timer(void) {
int v1 = 0, v2 = 0, v3 = 0;
@ -1964,14 +1974,19 @@ static void test_util(void) {
free(s);
}
ASSERT(mg_to64(mg_str("-9223372036854775809")) == 0);
ASSERT(mg_to64(mg_str("9223372036854775800")) == 0);
ASSERT(mg_to64(mg_str("9223372036854775700")) > 0);
ASSERT(mg_tou64(mg_str("0")) == 0);
ASSERT(mg_tou64(mg_str("123")) == 123);
ASSERT(mg_tou64(mg_str("")) == 0);
ASSERT(mg_tou64(mg_str("-")) == 0);
ASSERT(mg_tou64(mg_str("18446744073709551615")) == 18446744073709551615U);
{
extern bool mg_to_size_t(struct mg_str, size_t *);
size_t val = 1;
ASSERT(mg_to_size_t(mg_str("0"), &val) && val == 0);
ASSERT(mg_to_size_t(mg_str("123"), &val) && val == 123);
ASSERT(mg_to_size_t(mg_str(""), &val) && val == 0);
ASSERT(mg_to_size_t(mg_str("-"), &val) == false);
ASSERT(mg_to_size_t(mg_str("18446744073709551616"), &val) ==
false); // range +1
ASSERT(mg_to_size_t(mg_str("18446744073709551610"), &val) == false);
// TODO(): ASSERT(mg_to_size_t(mg_str("18446744073709551609"), &val) &&
// val == 18446744073709551609U); // our max or SIZE_MAX
}
{
size_t i;
@ -2865,7 +2880,9 @@ static void start_thread(void (*f)(void *), void *p) {
pthread_attr_destroy(&attr);
}
#else
static void start_thread(void (*f)(void *), void *p) { (void) f, (void) p; }
static void start_thread(void (*f)(void *), void *p) {
(void) f, (void) p;
}
#endif
static void test_queue(void) {