You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
9.8 KiB
160 lines
9.8 KiB
// Проверки HTTP codec на каждом возможном разбиении headers и побайтовом chunked body. |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include "proxy/http_proxy.h" |
|
#include "debug_config.h" |
|
#include "mem.h" |
|
|
|
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #x); exit(1); } } while (0) |
|
struct output { char bytes[100000]; size_t len, source; }; |
|
|
|
static int collect(void* arg, const uint8_t* data, size_t len, size_t source) { |
|
struct output* out = arg; |
|
CHECK(out->len + len < sizeof(out->bytes)); |
|
if (len) memcpy(out->bytes + out->len, data, len); |
|
out->len += len; out->source += source; out->bytes[out->len] = 0; |
|
return 0; |
|
} |
|
|
|
static int request(const char* text, struct http_proxy_message* m, struct http_proxy_request* r, struct output* out) { |
|
memset(m, 0, sizeof(*m)); memset(r, 0, sizeof(*r)); memset(out, 0, sizeof(*out)); |
|
size_t used; |
|
int ret = http_proxy_headers_feed(m, (const uint8_t*)text, strlen(text), &used); |
|
if (ret <= 0) return ret; |
|
return http_proxy_request_parse(m, r, collect, out); |
|
} |
|
|
|
static int response(const char* text, struct http_proxy_message* m, const struct http_proxy_request* r, struct output* out) { |
|
memset(m, 0, sizeof(*m)); memset(out, 0, sizeof(*out)); |
|
size_t used; |
|
int ret = http_proxy_headers_feed(m, (const uint8_t*)text, strlen(text), &used); |
|
if (ret <= 0) return ret; |
|
return http_proxy_response_parse(m, r, r->close, collect, out); |
|
} |
|
|
|
static void header_tests(void) { |
|
const char text[] = "POST http://example.org:8080?x=1 HTTP/1.1\r\nHost: wrong.example\r\n" |
|
"Proxy-Authorization: Basic secret\r\nConnection: X-Private, keep-alive\r\n" |
|
"X-Private: secret\r\nAuthorization: Bearer origin\r\nContent-Length: 3\r\n\r\nabcNEXT"; |
|
size_t header_len = strstr(text, "\r\n\r\n") + 4 - text; |
|
for (size_t split = 0; split <= header_len; split++) { |
|
struct http_proxy_message m = {0}; struct http_proxy_request r = {0}; struct output out = {0}; size_t used; |
|
CHECK(http_proxy_headers_feed(&m, (const uint8_t*)text, split, &used) == (split == header_len)); |
|
CHECK(used == split && out.source == 0); |
|
CHECK(http_proxy_headers_feed(&m, (const uint8_t*)text + split, sizeof(text)-1-split, &used) == 1); |
|
CHECK(used == header_len - split); |
|
CHECK(http_proxy_request_parse(&m, &r, collect, &out) == 0); |
|
CHECK(!strcmp(r.host, "example.org") && r.port == 8080); |
|
CHECK(strstr(out.bytes, "POST /?x=1 HTTP/1.1\r\nHost: example.org:8080\r\n")); |
|
CHECK(!strstr(out.bytes, "secret") && !strstr(out.bytes, "wrong.example")); |
|
CHECK(strstr(out.bytes, "Authorization: Bearer origin\r\n")); |
|
CHECK(strstr(out.bytes, "Connection: close\r\n") && out.source == header_len); |
|
CHECK(http_proxy_body_feed(&m, (const uint8_t*)text + header_len, 7, &used, collect, &out) == 1); |
|
CHECK(used == 3 && out.source == header_len + 3 && !strcmp(out.bytes + out.len - 3, "abc")); |
|
} |
|
struct http_proxy_message m; struct http_proxy_request r; struct output out; |
|
CHECK(request("OPTIONS http://example.org HTTP/1.1\r\nHost: ignored\r\n\r\n", &m, &r, &out) == 0); |
|
CHECK(strstr(out.bytes, "OPTIONS * HTTP/1.1\r\n")); |
|
CHECK(request("CONNECT example.org:443 HTTP/1.1\r\nHost: ignored\r\n\r\n", &m, &r, &out) == 0); |
|
CHECK(r.connect && r.port == 443 && out.len == 0); |
|
const char* invalid[] = { |
|
"CONNECT example.org:443junk HTTP/1.1\r\nHost: x\r\n\r\n", |
|
"CONNECT example.org:443 GARBAGE\r\nHost: x\r\n\r\n", |
|
"CONNECT example.org HTTP/1.1\r\nHost: x\r\n\r\n", |
|
"CONNECT example.org:443 HTTP/1.1\r\nHost: x\r\nContent-Length: 1\r\n\r\n", |
|
"GET http://x/ HTTP/1.1\r\n\r\n", |
|
"GET http://x/ HTTP/1.1\r\nHost: x\r\nHost: x\r\n\r\n", |
|
"GET http://x/ HTTP/1.1\r\nHost : x\r\n\r\n", |
|
"GET http://x/ HTTP/1.1\nHost: x\n\n", |
|
"GET http://x/ HTTP/1.1\r\nHost: x\r\nX: a\r\n folded\r\n\r\n", |
|
"POST http://x/ HTTP/1.1\r\nHost: x\r\nContent-Length: 1\r\nContent-Length: 2\r\n\r\n", |
|
"POST http://x/ HTTP/1.1\r\nHost: x\r\nContent-Length: 18446744073709551616\r\n\r\n", |
|
"POST http://x/ HTTP/1.1\r\nHost: x\r\nContent-Length: +1\r\n\r\n", |
|
"POST http://x/ HTTP/1.1\r\nHost: x\r\nContent-Length: 1\r\nTransfer-Encoding: chunked\r\n\r\n", |
|
"POST http://x/ HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked, chunked\r\n\r\n", |
|
"POST http://x/ HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: gzip\r\n\r\n", |
|
"GET http://x/ HTTP/1.1\r\nHost: x\r\nConnection: Host\r\n\r\n", |
|
"GET http://x/ HTTP/1.1\r\nHost: x\r\nConnection: Upgrade\r\nUpgrade: bad/version/extra\r\n\r\n", |
|
"GET http://user@x/ HTTP/1.1\r\nHost: x\r\n\r\n", |
|
"GET http://x:65536/ HTTP/1.1\r\nHost: x\r\n\r\n", |
|
"GET http://x:0/ HTTP/1.1\r\nHost: x\r\n\r\n", |
|
"GET http://x/#fragment HTTP/1.1\r\nHost: x\r\n\r\n" |
|
}; |
|
for (size_t i = 0; i < sizeof(invalid)/sizeof(invalid[0]); i++) { |
|
CHECK(request(invalid[i], &m, &r, &out) == -400 && out.len == 0); |
|
} |
|
CHECK(request("GET http://[::1]/ HTTP/1.1\r\nHost: [::1]\r\n\r\n", &m, &r, &out) == -502); |
|
char long_text[40000]; int n = snprintf(long_text, sizeof(long_text), "GET http://x/"); |
|
memset(long_text+n, 'a', 3000); n += 3000; |
|
snprintf(long_text+n, sizeof(long_text)-n, " HTTP/1.1\r\nHost: x\r\n\r\n"); |
|
CHECK(request(long_text, &m, &r, &out) == 0); |
|
n = snprintf(long_text, sizeof(long_text), "POST http://x/ HTTP/1.1\r\nHost: x\r\nX-Pad: "); |
|
memset(long_text+n, 'a', 30000); n += 30000; |
|
snprintf(long_text+n, sizeof(long_text)-n, "\r\nContent-Length: 2000\r\n\r\n"); |
|
CHECK(request(long_text, &m, &r, &out) == 0); |
|
n = snprintf(long_text, sizeof(long_text), "GET http://x/ HTTP/1.1\r\nHost: x\r\nX-Pad: "); |
|
memset(long_text+n, 'a', 33000); long_text[n+33000] = 0; |
|
CHECK(request(long_text, &m, &r, &out) == -431); |
|
puts("[PASS] HTTP strict headers, absolute URL/Host, hop fields, arbitrary splits and limits"); |
|
} |
|
|
|
static void body_tests(void) { |
|
struct http_proxy_message m; struct http_proxy_request r; struct output out; |
|
const char header[] = "POST http://x/ HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\nConnection: X-Hop\r\n\r\n"; |
|
const char body[] = "3 ; foo=\"x\\\"y\"\r\nabc\r\n2\r\nde\r\n0\r\nX-End: yes\r\nX-Hop: secret\r\nProxy-Authorization: secret\r\n\r\n"; |
|
CHECK(request(header, &m, &r, &out) == 0); |
|
size_t head_out = out.len; |
|
for (size_t i = 0; i < sizeof(body)-1; i++) { |
|
size_t used; |
|
CHECK(http_proxy_body_feed(&m, (const uint8_t*)body+i, 1, &used, collect, &out) == (i == sizeof(body)-2)); |
|
CHECK(used == 1 && out.source <= strlen(header)+i+1); |
|
} |
|
CHECK(out.source == strlen(header)+strlen(body)); |
|
CHECK(!strcmp(out.bytes+head_out, "3 ; foo=\"x\\\"y\"\r\nabc\r\n2\r\nde\r\n0\r\nX-End: yes\r\n\r\n")); |
|
const char* bad[] = {"Z\r\n", "10000000000000000\r\n", "1\r\na!", "1\na", "0\r\nContent-Length: 1\r\n\r\n", |
|
"0\r\nHost: x\r\n\r\n", "0\r\n folded\r\n\r\n", "1;foo=\"bad\r\n"}; |
|
for (size_t i = 0; i < sizeof(bad)/sizeof(bad[0]); i++) { |
|
size_t used; CHECK(request(header, &m, &r, &out) == 0); |
|
CHECK(http_proxy_body_feed(&m, (const uint8_t*)bad[i], strlen(bad[i]), &used, collect, &out) == -400); |
|
} |
|
r = (struct http_proxy_request){.minor=0,.close=1}; |
|
const char reply[] = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"; |
|
CHECK(response(reply, &m, &r, &out) == 0 && m.decode_chunks); |
|
CHECK(!strstr(out.bytes, "Transfer-Encoding") && strstr(out.bytes, "HTTP/1.0 200 OK\r\n")); |
|
head_out = out.len; |
|
for (size_t i = 0; i < sizeof(body)-1; i++) { |
|
size_t used; CHECK(http_proxy_body_feed(&m, (const uint8_t*)body+i, 1, &used, collect, &out) >= 0); |
|
} |
|
CHECK(!strcmp(out.bytes+head_out, "abcde") && out.source == strlen(reply)+strlen(body)); |
|
puts("[PASS] chunked boundaries, extensions/trailers, source byte credit and HTTP/1.0 decoding"); |
|
} |
|
|
|
static void response_tests(void) { |
|
struct http_proxy_message m; struct output out; struct http_proxy_request r = {.minor=1}; |
|
CHECK(response("HTTP/1.1 100 Continue\r\n\r\n", &m, &r, &out) == 0 && m.body == HTTP_BODY_DONE); |
|
CHECK(response("HTTP/1.1 204 No Content\r\n\r\n", &m, &r, &out) == 0 && m.body == HTTP_BODY_DONE); |
|
CHECK(response("HTTP/1.1 304 Not Modified\r\nContent-Length: 999\r\n\r\n", &m, &r, &out) == 0 && m.body == HTTP_BODY_DONE); |
|
CHECK(response("HTTP/1.1 200 OK\r\n\r\n", &m, &r, &out) == 0 && m.body == HTTP_BODY_EOF && m.close); |
|
CHECK(strstr(out.bytes, "Connection: close\r\n")); |
|
r.head = 1; |
|
CHECK(response("HTTP/1.1 200 OK\r\nContent-Length: 999\r\n\r\n", &m, &r, &out) == 0 && m.body == HTTP_BODY_DONE); |
|
r.head = 0; |
|
CHECK(response("HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n", &m, &r, &out) == -502 && out.len == 0); |
|
CHECK(response("HTTP/1.1 200 OK\r\nContent-Length: 2\r\nTransfer-Encoding: chunked\r\n\r\n", &m, &r, &out) == -502); |
|
CHECK(response("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n", &m, &r, &out) == -502); |
|
strcpy(r.upgrade, "websocket, example/1.2"); |
|
CHECK(response("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: example/1.2\r\n\r\n", &m, &r, &out) == 0); |
|
CHECK(strstr(out.bytes, "Connection: Upgrade\r\nUpgrade: example/1.2\r\n")); |
|
CHECK(response("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: other\r\n\r\n", &m, &r, &out) == -502); |
|
r.minor = 0; r.close = 1; r.upgrade[0] = 0; |
|
CHECK(response("HTTP/1.1 100 Continue\r\n\r\n", &m, &r, &out) == 0 && out.len == 0 && out.source > 0); |
|
puts("[PASS] HEAD/1xx/204/304/EOF response framing and negotiated Upgrade"); |
|
} |
|
|
|
int main(void) { |
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); |
|
header_tests(); body_tests(); response_tests(); |
|
CHECK(u_get_allocated_count() == 0); |
|
return 0; |
|
}
|
|
|