diff --git a/mongoose.c b/mongoose.c index 4c2d80bf..fd8ca76c 100644 --- a/mongoose.c +++ b/mongoose.c @@ -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 { diff --git a/src/http.c b/src/http.c index 900fbbe1..113dad62 100644 --- a/src/http.c +++ b/src/http.c @@ -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; diff --git a/src/ws.c b/src/ws.c index 5c57fed2..461d31f9 100644 --- a/src/ws.c +++ b/src/ws.c @@ -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 { diff --git a/test/unit_test.c b/test/unit_test.c index 2a2fe3f5..17ca7238 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -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();