mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-21 11:30:01 +00:00
Fix #2292 - fail on stray \r character in headers
This commit is contained in:
1 parent
bf8c4dfc83
commit
88e880f107
4 files changed
+131
-66
No files matched your search
+49
-31
@@ -1412,16 +1412,6 @@ int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *skip(const char *s, const char *e, const char *d,
|
||||
struct mg_str *v) {
|
||||
v->ptr = s;
|
||||
while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
|
||||
v->len = (size_t) (s - v->ptr);
|
||||
while (s < e && strchr(d, *s) != NULL) s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
|
||||
size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
|
||||
for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
|
||||
@@ -1431,22 +1421,42 @@ struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get character length. Used to parse method, URI, headers
|
||||
static size_t clen(const char *s) {
|
||||
uint8_t c = *(uint8_t *) s;
|
||||
if (c > ' ' && c < '~') return 1; // Usual ascii printed char
|
||||
if ((c & 0xe0) == 0xc0) return 2; // 2-byte UTF8
|
||||
if ((c & 0xf0) == 0xe0) return 3; // 3-byte UTF8
|
||||
if ((c & 0xf8) == 0xf0) return 4; // 4-byte UTF8
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip until the newline. Return advanced `s`, or NULL on error
|
||||
static const char *skiptorn(const char *s, const char *end, struct mg_str *v) {
|
||||
v->ptr = s;
|
||||
while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline
|
||||
if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r
|
||||
if (s < end && s[0] == '\r') s++; // Skip \r
|
||||
if (s >= end || *s++ != '\n') return NULL; // Skip \n
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool mg_http_parse_headers(const char *s, const char *end,
|
||||
struct mg_http_header *h, int max_headers) {
|
||||
int i;
|
||||
for (i = 0; i < max_headers; i++) {
|
||||
struct mg_str k, v, tmp;
|
||||
const char *he = skip(s, end, "\r\n", &tmp);
|
||||
if (tmp.len == 0) break; // empty header = EOH
|
||||
s = skip(s, he, ": \r\n", &k);
|
||||
if (k.ptr[k.len] != ':') return false; // Invalid header
|
||||
s = skip(s, he, "\r\n", &v);
|
||||
struct mg_http_header *h, size_t max_hdrs) {
|
||||
size_t i, n;
|
||||
for (i = 0; i < max_hdrs; i++) {
|
||||
struct mg_str k = {NULL, 0}, v = {NULL, 0};
|
||||
if (s >= end) return false;
|
||||
if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
|
||||
k.ptr = s;
|
||||
while (s < end && s[0] != ':' && (n = clen(s)) > 0) s += n, k.len += n;
|
||||
if (k.len == 0) return false; // Empty name
|
||||
if (s >= end || *s++ != ':') return false; // Invalid, not followed by :
|
||||
while (s < end && s[0] == ' ') s++; // Skip spaces
|
||||
if ((s = skiptorn(s, end, &v)) == NULL) return false;
|
||||
while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces
|
||||
if (k.len == 0) return false; // empty name
|
||||
// MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len, tmp.ptr,
|
||||
//(int) k.len, k.ptr, (int) v.len, v.ptr));
|
||||
h[i].name = k;
|
||||
h[i].value = v;
|
||||
// MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.ptr, (int) v.len, v.ptr));
|
||||
h[i].name = k, h[i].value = v; // Success. Assign values
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1455,6 +1465,7 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
|
||||
int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
|
||||
const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL
|
||||
struct mg_str *cl;
|
||||
size_t n;
|
||||
|
||||
memset(hm, 0, sizeof(*hm));
|
||||
if (req_len <= 0) return req_len;
|
||||
@@ -1466,9 +1477,13 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
|
||||
hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite
|
||||
|
||||
// Parse request line
|
||||
s = skip(s, end, " ", &hm->method);
|
||||
s = skip(s, end, " ", &hm->uri);
|
||||
s = skip(s, end, "\r\n", &hm->proto);
|
||||
hm->method.ptr = s;
|
||||
while (s < end && (n = clen(s)) > 0) s += n, hm->method.len += n;
|
||||
while (s < end && s[0] == ' ') s++; // Skip spaces
|
||||
hm->uri.ptr = s;
|
||||
while (s < end && (n = clen(s)) > 0) s += n, hm->uri.len += n;
|
||||
while (s < end && s[0] == ' ') s++; // Skip spaces
|
||||
if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
|
||||
|
||||
// Sanity check. Allow protocol/reason to be empty
|
||||
if (hm->method.len == 0 || hm->uri.len == 0) return -1;
|
||||
@@ -6263,12 +6278,15 @@ static bool mg_ws_client_handshake(struct mg_connection *c) {
|
||||
mg_error(c, "not http"); // Some just, not an HTTP request
|
||||
} else if (n > 0) {
|
||||
if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
|
||||
mg_error(c, "handshake error");
|
||||
mg_error(c, "ws handshake error");
|
||||
} else {
|
||||
struct mg_http_message hm;
|
||||
mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
|
||||
c->is_websocket = 1;
|
||||
mg_call(c, MG_EV_WS_OPEN, &hm);
|
||||
if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
|
||||
c->is_websocket = 1;
|
||||
mg_call(c, MG_EV_WS_OPEN, &hm);
|
||||
} else {
|
||||
mg_error(c, "ws handshake error");
|
||||
}
|
||||
}
|
||||
mg_iobuf_del(&c->recv, 0, (size_t) n);
|
||||
} else {
|
||||
|
||||
+42
-27
@@ -180,16 +180,6 @@ int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *skip(const char *s, const char *e, const char *d,
|
||||
struct mg_str *v) {
|
||||
v->ptr = s;
|
||||
while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
|
||||
v->len = (size_t) (s - v->ptr);
|
||||
while (s < e && strchr(d, *s) != NULL) s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
|
||||
size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
|
||||
for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
|
||||
@@ -199,22 +189,42 @@ struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get character length. Used to parse method, URI, headers
|
||||
static size_t clen(const char *s) {
|
||||
uint8_t c = *(uint8_t *) s;
|
||||
if (c > ' ' && c < '~') return 1; // Usual ascii printed char
|
||||
if ((c & 0xe0) == 0xc0) return 2; // 2-byte UTF8
|
||||
if ((c & 0xf0) == 0xe0) return 3; // 3-byte UTF8
|
||||
if ((c & 0xf8) == 0xf0) return 4; // 4-byte UTF8
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip until the newline. Return advanced `s`, or NULL on error
|
||||
static const char *skiptorn(const char *s, const char *end, struct mg_str *v) {
|
||||
v->ptr = s;
|
||||
while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline
|
||||
if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r
|
||||
if (s < end && s[0] == '\r') s++; // Skip \r
|
||||
if (s >= end || *s++ != '\n') return NULL; // Skip \n
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool mg_http_parse_headers(const char *s, const char *end,
|
||||
struct mg_http_header *h, int max_headers) {
|
||||
int i;
|
||||
for (i = 0; i < max_headers; i++) {
|
||||
struct mg_str k, v, tmp;
|
||||
const char *he = skip(s, end, "\r\n", &tmp);
|
||||
if (tmp.len == 0) break; // empty header = EOH
|
||||
s = skip(s, he, ": \r\n", &k);
|
||||
if (k.ptr[k.len] != ':') return false; // Invalid header
|
||||
s = skip(s, he, "\r\n", &v);
|
||||
struct mg_http_header *h, size_t max_hdrs) {
|
||||
size_t i, n;
|
||||
for (i = 0; i < max_hdrs; i++) {
|
||||
struct mg_str k = {NULL, 0}, v = {NULL, 0};
|
||||
if (s >= end) return false;
|
||||
if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
|
||||
k.ptr = s;
|
||||
while (s < end && s[0] != ':' && (n = clen(s)) > 0) s += n, k.len += n;
|
||||
if (k.len == 0) return false; // Empty name
|
||||
if (s >= end || *s++ != ':') return false; // Invalid, not followed by :
|
||||
while (s < end && s[0] == ' ') s++; // Skip spaces
|
||||
if ((s = skiptorn(s, end, &v)) == NULL) return false;
|
||||
while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces
|
||||
if (k.len == 0) return false; // empty name
|
||||
// MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len, tmp.ptr,
|
||||
//(int) k.len, k.ptr, (int) v.len, v.ptr));
|
||||
h[i].name = k;
|
||||
h[i].value = v;
|
||||
// MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.ptr, (int) v.len, v.ptr));
|
||||
h[i].name = k, h[i].value = v; // Success. Assign values
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -223,6 +233,7 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
|
||||
int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
|
||||
const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL
|
||||
struct mg_str *cl;
|
||||
size_t n;
|
||||
|
||||
memset(hm, 0, sizeof(*hm));
|
||||
if (req_len <= 0) return req_len;
|
||||
@@ -234,9 +245,13 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
|
||||
hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite
|
||||
|
||||
// Parse request line
|
||||
s = skip(s, end, " ", &hm->method);
|
||||
s = skip(s, end, " ", &hm->uri);
|
||||
s = skip(s, end, "\r\n", &hm->proto);
|
||||
hm->method.ptr = s;
|
||||
while (s < end && (n = clen(s)) > 0) s += n, hm->method.len += n;
|
||||
while (s < end && s[0] == ' ') s++; // Skip spaces
|
||||
hm->uri.ptr = s;
|
||||
while (s < end && (n = clen(s)) > 0) s += n, hm->uri.len += n;
|
||||
while (s < end && s[0] == ' ') s++; // Skip spaces
|
||||
if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
|
||||
|
||||
// Sanity check. Allow protocol/reason to be empty
|
||||
if (hm->method.len == 0 || hm->uri.len == 0) return -1;
|
||||
|
||||
@@ -146,12 +146,15 @@ static bool mg_ws_client_handshake(struct mg_connection *c) {
|
||||
mg_error(c, "not http"); // Some just, not an HTTP request
|
||||
} else if (n > 0) {
|
||||
if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
|
||||
mg_error(c, "handshake error");
|
||||
mg_error(c, "ws handshake error");
|
||||
} else {
|
||||
struct mg_http_message hm;
|
||||
mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
|
||||
c->is_websocket = 1;
|
||||
mg_call(c, MG_EV_WS_OPEN, &hm);
|
||||
if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
|
||||
c->is_websocket = 1;
|
||||
mg_call(c, MG_EV_WS_OPEN, &hm);
|
||||
} else {
|
||||
mg_error(c, "ws handshake error");
|
||||
}
|
||||
}
|
||||
mg_iobuf_del(&c->recv, 0, (size_t) n);
|
||||
} else {
|
||||
|
||||
+33
-4
@@ -745,7 +745,7 @@ static int cmpbody(const char *buf, const char *str) {
|
||||
struct mg_str s = mg_str(str);
|
||||
struct mg_http_message hm = gethm(buf);
|
||||
size_t len = strlen(buf);
|
||||
mg_http_parse(buf, len, &hm);
|
||||
// mg_http_parse(buf, len, &hm);
|
||||
if (hm.body.len > len) hm.body.len = len - (size_t) (hm.body.ptr - buf);
|
||||
return mg_strcmp(hm.body, s);
|
||||
}
|
||||
@@ -857,6 +857,7 @@ static void test_http_server(void) {
|
||||
|
||||
// Fetch file with unicode chars in filename
|
||||
ASSERT(fetch(&mgr, buf, url, "GET /київ.txt HTTP/1.0\n\n") == 200);
|
||||
MG_INFO(("%s", buf));
|
||||
ASSERT(cmpbody(buf, "є\n") == 0);
|
||||
|
||||
ASSERT(fetch(&mgr, buf, url, "GET /../fuzz.c HTTP/1.0\n\n") == 400);
|
||||
@@ -1350,6 +1351,9 @@ static void test_http_parse(void) {
|
||||
ASSERT(req.query.len == 0);
|
||||
ASSERT(req.message.len == len);
|
||||
ASSERT(req.body.len == 0);
|
||||
ASSERT(mg_vcmp(&req.method, "GET") == 0);
|
||||
ASSERT(mg_vcmp(&req.uri, "/blah") == 0);
|
||||
ASSERT(mg_vcmp(&req.proto, "HTTP/1.0") == 0);
|
||||
for (idx = 0; idx < len; idx++) ASSERT(mg_http_parse(s, idx, &req) == 0);
|
||||
}
|
||||
|
||||
@@ -1386,6 +1390,30 @@ static void test_http_parse(void) {
|
||||
ASSERT(req.body.len == 0);
|
||||
}
|
||||
|
||||
// #2292: fail on stray \r inside the headers
|
||||
ASSERT(mg_http_parse("a є\n\n", 6, &req) > 0);
|
||||
ASSERT(mg_http_parse("a b\n\n", 5, &req) > 0);
|
||||
ASSERT(mg_http_parse("a b\na:\n\n", 8, &req) > 0);
|
||||
ASSERT(mg_http_parse("a b\na:\r\n\n", 9, &req) > 0);
|
||||
ASSERT(mg_http_parse("a b\n\ra:\r\n\n", 10, &req) == -1);
|
||||
ASSERT(mg_http_parse("a b\na:\r1\n\n", 10, &req) == -1);
|
||||
ASSERT(mg_http_parse("a b\na: \r1\n\n", 11, &req) == -1);
|
||||
ASSERT(mg_http_parse("a b\na: \rb:\n\n", 12, &req) == -1);
|
||||
ASSERT(mg_http_parse("a b\na: \nb:\n\n", 12, &req) > 0);
|
||||
|
||||
{
|
||||
const char *s = "ґєт /слеш вах вах\nмісто: кіїв \n\n";
|
||||
ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s));
|
||||
ASSERT(req.body.len == 0);
|
||||
ASSERT(req.headers[1].name.len == 0);
|
||||
ASSERT(mg_vcmp(&req.headers[0].name, "місто") == 0);
|
||||
ASSERT(mg_vcmp(&req.headers[0].value, "кіїв") == 0);
|
||||
ASSERT((v = mg_http_get_header(&req, "місто")) != NULL);
|
||||
ASSERT(mg_vcmp(&req.method, "ґєт") == 0);
|
||||
ASSERT(mg_vcmp(&req.uri, "/слеш") == 0);
|
||||
ASSERT(mg_vcmp(&req.proto, "вах вах") == 0);
|
||||
}
|
||||
|
||||
{
|
||||
const char *s = "a b c\r\nContent-Length: 21 \r\nb: t\r\nv:v\r\n\r\nabc";
|
||||
ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s) - 3);
|
||||
@@ -2570,7 +2598,7 @@ static void test_udp(void) {
|
||||
}
|
||||
|
||||
static void test_check_ip_acl(void) {
|
||||
struct mg_addr ip = {{1,2,3,4}, 0, false}; // 1.2.3.4
|
||||
struct mg_addr ip = {{1, 2, 3, 4}, 0, false}; // 1.2.3.4
|
||||
ASSERT(mg_check_ip_acl(mg_str(NULL), &ip) == 1);
|
||||
ASSERT(mg_check_ip_acl(mg_str(""), &ip) == 1);
|
||||
ASSERT(mg_check_ip_acl(mg_str("invalid"), &ip) == -1);
|
||||
@@ -2581,7 +2609,8 @@ static void test_check_ip_acl(void) {
|
||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.2.3.4"), &ip) == 1);
|
||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/16"), &ip) == 0);
|
||||
ip.is_ip6 = true;
|
||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) == -1); // not yet supported
|
||||
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) ==
|
||||
-1); // not yet supported
|
||||
}
|
||||
|
||||
static void w3(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
|
||||
@@ -3057,6 +3086,7 @@ int main(void) {
|
||||
test_str();
|
||||
test_globmatch();
|
||||
test_get_header_var();
|
||||
test_http_parse();
|
||||
test_rewrites();
|
||||
test_check_ip_acl();
|
||||
test_udp();
|
||||
@@ -3068,7 +3098,6 @@ int main(void) {
|
||||
test_http_chunked();
|
||||
test_http_upload();
|
||||
test_http_stream_buffer();
|
||||
test_http_parse();
|
||||
test_util();
|
||||
test_dns();
|
||||
test_timer();
|
||||
|
||||
Reference in new issue
Block a user