Add support for Transfer-Encoding
This commit is contained in:
@@ -10,3 +10,4 @@ coverage_report
|
|||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
*.so
|
*.so
|
||||||
|
*.pem
|
||||||
@@ -21,6 +21,12 @@ else
|
|||||||
LFLAGS += -lssl -lcrypto
|
LFLAGS += -lssl -lcrypto
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
HTTPS = 1
|
||||||
|
ifeq ($(HTTPS),1)
|
||||||
|
CFLAGS += -DHTTPS_ENABLED
|
||||||
|
LFLAGS += -lssl -lcrypto
|
||||||
|
endif
|
||||||
|
|
||||||
# Installation directories
|
# Installation directories
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
LIBDIR = $(PREFIX)/lib
|
LIBDIR = $(PREFIX)/lib
|
||||||
|
|||||||
@@ -365,6 +365,8 @@ void print_bytes(HTTP_String prefix, HTTP_String src)
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
#ifndef HTTP_AMALGAMATION
|
||||||
#include "parse.h"
|
#include "parse.h"
|
||||||
@@ -372,43 +374,43 @@ void print_bytes(HTTP_String prefix, HTTP_String src)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// From RFC 9112
|
// From RFC 9112
|
||||||
// request-target = origin-form
|
// request-target = origin-form
|
||||||
// / absolute-form
|
// / absolute-form
|
||||||
// / authority-form
|
// / authority-form
|
||||||
// / asterisk-form
|
// / asterisk-form
|
||||||
// origin-form = absolute-path [ "?" query ]
|
// origin-form = absolute-path [ "?" query ]
|
||||||
// absolute-form = absolute-URI
|
// absolute-form = absolute-URI
|
||||||
// authority-form = uri-host ":" port
|
// authority-form = uri-host ":" port
|
||||||
// asterisk-form = "*"
|
// asterisk-form = "*"
|
||||||
//
|
//
|
||||||
// From RFC 9110
|
// From RFC 9110
|
||||||
// URI-reference = <URI-reference, see [URI], Section 4.1>
|
// URI-reference = <URI-reference, see [URI], Section 4.1>
|
||||||
// absolute-URI = <absolute-URI, see [URI], Section 4.3>
|
// absolute-URI = <absolute-URI, see [URI], Section 4.3>
|
||||||
// relative-part = <relative-part, see [URI], Section 4.2>
|
// relative-part = <relative-part, see [URI], Section 4.2>
|
||||||
// authority = <authority, see [URI], Section 3.2>
|
// authority = <authority, see [URI], Section 3.2>
|
||||||
// uri-host = <host, see [URI], Section 3.2.2>
|
// uri-host = <host, see [URI], Section 3.2.2>
|
||||||
// port = <port, see [URI], Section 3.2.3>
|
// port = <port, see [URI], Section 3.2.3>
|
||||||
// path-abempty = <path-abempty, see [URI], Section 3.3>
|
// path-abempty = <path-abempty, see [URI], Section 3.3>
|
||||||
// segment = <segment, see [URI], Section 3.3>
|
// segment = <segment, see [URI], Section 3.3>
|
||||||
// query = <query, see [URI], Section 3.4>
|
// query = <query, see [URI], Section 3.4>
|
||||||
//
|
//
|
||||||
// absolute-path = 1*( "/" segment )
|
// absolute-path = 1*( "/" segment )
|
||||||
// partial-URI = relative-part [ "?" query ]
|
// partial-URI = relative-part [ "?" query ]
|
||||||
//
|
//
|
||||||
// From RFC 3986:
|
// From RFC 3986:
|
||||||
// segment = *pchar
|
// segment = *pchar
|
||||||
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||||
// pct-encoded = "%" HEXDIG HEXDIG
|
// pct-encoded = "%" HEXDIG HEXDIG
|
||||||
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
// / "*" / "+" / "," / ";" / "="
|
// / "*" / "+" / "," / ";" / "="
|
||||||
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
// query = *( pchar / "/" / "?" )
|
// query = *( pchar / "/" / "?" )
|
||||||
// absolute-URI = scheme ":" hier-part [ "?" query ]
|
// absolute-URI = scheme ":" hier-part [ "?" query ]
|
||||||
// hier-part = "//" authority path-abempty
|
// hier-part = "//" authority path-abempty
|
||||||
// / path-absolute
|
// / path-absolute
|
||||||
// / path-rootless
|
// / path-rootless
|
||||||
// / path-empty
|
// / path-empty
|
||||||
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *src;
|
char *src;
|
||||||
@@ -472,6 +474,10 @@ static int is_vchar(char c)
|
|||||||
return c >= ' ' && c <= '~';
|
return c >= ' ' && c <= '~';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CONSUME_OPTIONAL_SEQUENCE(scanner, func) \
|
||||||
|
while ((scanner)->cur < (scanner)->len && (func)((scanner)->src[(scanner)->cur])) \
|
||||||
|
(scanner)->cur++;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
consume_absolute_path(Scanner *s)
|
consume_absolute_path(Scanner *s)
|
||||||
{
|
{
|
||||||
@@ -481,8 +487,7 @@ consume_absolute_path(Scanner *s)
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
while (s->cur < s->len && is_pchar(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar);
|
||||||
s->cur++;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '/')
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
||||||
break;
|
break;
|
||||||
@@ -519,8 +524,7 @@ static int parse_path(Scanner *s, HTTP_String *path, int abempty)
|
|||||||
s->cur++;
|
s->cur++;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
while (s->cur < s->len && is_pchar(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar);
|
||||||
s->cur++;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '/')
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
||||||
break;
|
break;
|
||||||
@@ -534,8 +538,7 @@ static int parse_path(Scanner *s, HTTP_String *path, int abempty)
|
|||||||
s->cur++;
|
s->cur++;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
while (s->cur < s->len && is_pchar(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar)
|
||||||
s->cur++;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '/')
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
||||||
break;
|
break;
|
||||||
@@ -815,8 +818,9 @@ static int parse_authority(Scanner *s, HTTP_Authority *authority)
|
|||||||
HTTP_String userinfo;
|
HTTP_String userinfo;
|
||||||
{
|
{
|
||||||
int start = s->cur;
|
int start = s->cur;
|
||||||
while (s->cur < s->len && is_userinfo(s->src[s->cur]))
|
|
||||||
s->cur++;
|
CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo);
|
||||||
|
|
||||||
if (s->cur < s->len && s->src[s->cur] == '@') {
|
if (s->cur < s->len && s->src[s->cur] == '@') {
|
||||||
userinfo = (HTTP_String) {
|
userinfo = (HTTP_String) {
|
||||||
s->src + start,
|
s->src + start,
|
||||||
@@ -1014,6 +1018,21 @@ static int parse_request_target(Scanner *s, HTTP_URL *url)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool consume_str(Scanner *scan, HTTP_String token)
|
||||||
|
{
|
||||||
|
HTTP_ASSERT(token.len > 0);
|
||||||
|
|
||||||
|
if (token.len > scan->len - scan->cur)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < token.len; i++)
|
||||||
|
if (scan->src[scan->cur + i] != token.ptr[i])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
scan->cur += token.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int is_header_body(char c)
|
static int is_header_body(char c)
|
||||||
{
|
{
|
||||||
return is_vchar(c) || c == ' ' || c == '\t';
|
return is_vchar(c) || c == ' ' || c == '\t';
|
||||||
@@ -1022,16 +1041,9 @@ static int is_header_body(char c)
|
|||||||
static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
||||||
{
|
{
|
||||||
int num_headers = 0;
|
int num_headers = 0;
|
||||||
for (;;) {
|
while (!consume_str(s, HTTP_STR("\r\n"))) {
|
||||||
|
|
||||||
if (s->len - s->cur > 1
|
// RFC 9112:
|
||||||
&& s->src[s->cur+0] == '\r'
|
|
||||||
&& s->src[s->cur+1] == '\n') {
|
|
||||||
s->cur += 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 9112:
|
|
||||||
// field-line = field-name ":" OWS field-value OWS
|
// field-line = field-name ":" OWS field-value OWS
|
||||||
//
|
//
|
||||||
// RFC 9110:
|
// RFC 9110:
|
||||||
@@ -1055,196 +1067,354 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
|||||||
return -1; // ERROR
|
return -1; // ERROR
|
||||||
s->cur++;
|
s->cur++;
|
||||||
|
|
||||||
start = s->cur;
|
start = s->cur;
|
||||||
while (s->cur < s->len && is_header_body(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_header_body);
|
||||||
s->cur++;
|
|
||||||
HTTP_String body = { s->src + start, s->cur - start };
|
HTTP_String body = { s->src + start, s->cur - start };
|
||||||
body = http_trim(body);
|
body = http_trim(body);
|
||||||
|
|
||||||
if (s->len - s->cur < 2
|
if (num_headers < max_headers)
|
||||||
|| s->src[s->cur+0] != '\r'
|
headers[num_headers++] = (HTTP_Header) { name, body };
|
||||||
|| s->src[s->cur+1] != '\n')
|
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 2;
|
|
||||||
|
|
||||||
if (num_headers < max_headers)
|
if (!consume_str(s, HTTP_STR("\r\n"))) {
|
||||||
headers[num_headers++] = (HTTP_Header) { name, body };
|
return -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return num_headers;
|
return num_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TRANSFER_ENCODING_OPTION_CHUNKED,
|
||||||
|
TRANSFER_ENCODING_OPTION_COMPRESS,
|
||||||
|
TRANSFER_ENCODING_OPTION_DEFLATE,
|
||||||
|
TRANSFER_ENCODING_OPTION_GZIP,
|
||||||
|
} TransferEncodingOption;
|
||||||
|
|
||||||
|
static bool is_space(char c)
|
||||||
|
{
|
||||||
|
return c == ' ' || c == '\t';
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_content_length(const char *src, int len, unsigned long long *out)
|
parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
|
||||||
{
|
{
|
||||||
int cur = 0;
|
Scanner s = { src.ptr, src.len, 0 };
|
||||||
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
|
|
||||||
cur++;
|
|
||||||
|
|
||||||
if (cur == len || !is_digit(src[cur]))
|
int num = 0;
|
||||||
return -1;
|
for (;;) {
|
||||||
|
|
||||||
unsigned long long buf = 0;
|
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
||||||
do {
|
|
||||||
int d = src[cur++] - '0';
|
|
||||||
if (buf > (UINT64_MAX - d) / 10)
|
|
||||||
return -1;
|
|
||||||
buf = buf * 10 + d;
|
|
||||||
} while (cur < len && is_digit(src[cur]));
|
|
||||||
|
|
||||||
*out = buf;
|
TransferEncodingOption opt;
|
||||||
return 0;
|
if (0) {}
|
||||||
|
else if (consume_str(&s, HTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED;
|
||||||
|
else if (consume_str(&s, HTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS;
|
||||||
|
else if (consume_str(&s, HTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE;
|
||||||
|
else if (consume_str(&s, HTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP;
|
||||||
|
else return -1; // Invalid option
|
||||||
|
|
||||||
|
if (num == max)
|
||||||
|
return -1; // Too many options
|
||||||
|
dst[num++] = opt;
|
||||||
|
|
||||||
|
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
||||||
|
|
||||||
|
if (s.cur == s.len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (s.src[s.cur] != ',')
|
||||||
|
return -1; // Missing comma separator
|
||||||
|
}
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_content_length(const char *src, int len, uint64_t *out)
|
||||||
|
{
|
||||||
|
int cur = 0;
|
||||||
|
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (cur == len || !is_digit(src[cur]))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
uint64_t buf = 0;
|
||||||
|
do {
|
||||||
|
int d = src[cur++] - '0';
|
||||||
|
if (buf > (UINT64_MAX - d) / 10)
|
||||||
|
return -1;
|
||||||
|
buf = buf * 10 + d;
|
||||||
|
} while (cur < len && is_digit(src[cur]));
|
||||||
|
|
||||||
|
*out = buf;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_body(Scanner *s,
|
||||||
|
HTTP_Header *headers, int num_headers,
|
||||||
|
HTTP_String *body, bool body_expected)
|
||||||
|
{
|
||||||
|
|
||||||
|
// RFC 9112 section 6:
|
||||||
|
// The presence of a message body in a request is signaled by a Content-Length or
|
||||||
|
// Transfer-Encoding header field. Request message framing is independent of method
|
||||||
|
// semantics.
|
||||||
|
|
||||||
|
int header_index = http_find_header(headers, num_headers, HTTP_STR("Transfer-Encoding"));
|
||||||
|
if (header_index != -1) {
|
||||||
|
|
||||||
|
// RFC 9112 section 6.1:
|
||||||
|
// A server MAY reject a request that contains both Content-Length and Transfer-Encoding
|
||||||
|
// or process such a request in accordance with the Transfer-Encoding alone. Regardless,
|
||||||
|
// the server MUST close the connection after responding to such a request to avoid the
|
||||||
|
// potential attacks.
|
||||||
|
if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
HTTP_String value = headers[header_index].value;
|
||||||
|
|
||||||
|
// RFC 9112 section 6.1:
|
||||||
|
// If any transfer coding other than chunked is applied to a request's content, the
|
||||||
|
// sender MUST apply chunked as the final transfer coding to ensure that the message
|
||||||
|
// is properly framed. If any transfer coding other than chunked is applied to a
|
||||||
|
// response's content, the sender MUST either apply chunked as the final transfer
|
||||||
|
// coding or terminate the message by closing the connection.
|
||||||
|
|
||||||
|
TransferEncodingOption opts[8];
|
||||||
|
int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts));
|
||||||
|
if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
HTTP_String chunks_maybe[128];
|
||||||
|
HTTP_String *chunks = chunks_maybe;
|
||||||
|
int num_chunks = 0;
|
||||||
|
int max_chunks = HTTP_COUNT(chunks_maybe);
|
||||||
|
|
||||||
|
#define FREE_CHUNK_LIST \
|
||||||
|
if (chunks != chunks_maybe) \
|
||||||
|
free(chunks);
|
||||||
|
|
||||||
|
char *content_start = s->src + s->cur;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
// RFC 9112 section 7.1:
|
||||||
|
// The chunked transfer coding wraps content in order to transfer it as a series of chunks,
|
||||||
|
// each with its own size indicator, followed by an OPTIONAL trailer section containing
|
||||||
|
// trailer fields.
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_hex_digit(s->src[s->cur])) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chunk_len = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
char c = s->src[s->cur++];
|
||||||
|
int n = hex_digit_to_int(c);
|
||||||
|
if (chunk_len > (INT_MAX - n) / 16) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1; // overflow
|
||||||
|
}
|
||||||
|
chunk_len = chunk_len * 16 + n;
|
||||||
|
} while (s->cur < s->len && is_hex_digit(s->src[s->cur]));
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
if (s->src[s->cur] != '\r') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (s->src[s->cur] != '\n') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
char *chunk_ptr = s->src + s->cur;
|
||||||
|
|
||||||
|
if (chunk_len > s->len - s->cur) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
s->cur += chunk_len;
|
||||||
|
|
||||||
|
if (s->cur == s->len)
|
||||||
|
return 0; // Incomplete request
|
||||||
|
if (s->src[s->cur] != '\r') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
if (s->src[s->cur] != '\n') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
if (chunk_len == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (num_chunks == max_chunks) {
|
||||||
|
|
||||||
|
max_chunks *= 2;
|
||||||
|
|
||||||
|
HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_String));
|
||||||
|
if (new_chunks == NULL) {
|
||||||
|
if (chunks != chunks_maybe)
|
||||||
|
free(chunks);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_chunks; i++)
|
||||||
|
new_chunks[i] = chunks[i];
|
||||||
|
|
||||||
|
if (chunks != chunks_maybe)
|
||||||
|
free(chunks);
|
||||||
|
|
||||||
|
chunks = new_chunks;
|
||||||
|
}
|
||||||
|
chunks[num_chunks++] = (HTTP_String) { chunk_ptr, chunk_len };
|
||||||
|
}
|
||||||
|
|
||||||
|
char *content_ptr = content_start;
|
||||||
|
for (int i = 0; i < num_chunks; i++) {
|
||||||
|
memmove(content_ptr, chunks[i].ptr, chunks[i].len);
|
||||||
|
content_ptr += chunks[i].len;
|
||||||
|
}
|
||||||
|
|
||||||
|
*body = (HTTP_String) {
|
||||||
|
content_start,
|
||||||
|
content_ptr - content_start
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chunks != chunks_maybe)
|
||||||
|
free(chunks);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 9112 section 6.3:
|
||||||
|
// If a valid Content-Length header field is present without Transfer-Encoding,
|
||||||
|
// its decimal value defines the expected message body length in octets.
|
||||||
|
|
||||||
|
header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length"));
|
||||||
|
if (header_index != -1) {
|
||||||
|
|
||||||
|
// Have Content-Length
|
||||||
|
HTTP_String value = headers[header_index].value;
|
||||||
|
|
||||||
|
uint64_t tmp;
|
||||||
|
if (parse_content_length(value.ptr, value.len, &tmp) < 0)
|
||||||
|
return -1;
|
||||||
|
if (tmp > INT_MAX)
|
||||||
|
return -1;
|
||||||
|
int len = (int) tmp;
|
||||||
|
|
||||||
|
if (len > s->len - s->cur)
|
||||||
|
return 0; // Incomplete request
|
||||||
|
|
||||||
|
*body = (HTTP_String) { s->src + s->cur, len };
|
||||||
|
|
||||||
|
s->cur += len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No Content-Length or Transfer-Encoding
|
||||||
|
if (body_expected) return -1;
|
||||||
|
|
||||||
|
*body = (HTTP_String) { NULL, 0 };
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int contains_head(char *src, int len)
|
static int contains_head(char *src, int len)
|
||||||
{
|
{
|
||||||
int cur = 0;
|
int cur = 0;
|
||||||
while (len - cur > 3) {
|
while (len - cur > 3) {
|
||||||
if (src[cur+0] == '\r' &&
|
if (src[cur+0] == '\r' &&
|
||||||
src[cur+1] == '\n' &&
|
src[cur+1] == '\n' &&
|
||||||
src[cur+2] == '\r' &&
|
src[cur+2] == '\r' &&
|
||||||
src[cur+3] == '\n')
|
src[cur+3] == '\n')
|
||||||
return 1;
|
return 1;
|
||||||
cur++;
|
cur++;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_request(Scanner *s, HTTP_Request *req)
|
static int parse_request(Scanner *s, HTTP_Request *req)
|
||||||
{
|
{
|
||||||
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (s->len - s->cur >= 3
|
if (0) {}
|
||||||
&& s->src[s->cur+0] == 'G'
|
else if (consume_str(s, HTTP_STR("GET "))) req->method = HTTP_METHOD_GET;
|
||||||
&& s->src[s->cur+1] == 'E'
|
else if (consume_str(s, HTTP_STR("POST "))) req->method = HTTP_METHOD_POST;
|
||||||
&& s->src[s->cur+2] == 'T') {
|
else if (consume_str(s, HTTP_STR("PUT "))) req->method = HTTP_METHOD_PUT;
|
||||||
s->cur += 3;
|
else if (consume_str(s, HTTP_STR("HEAD "))) req->method = HTTP_METHOD_HEAD;
|
||||||
req->method = HTTP_METHOD_GET;
|
else if (consume_str(s, HTTP_STR("DELETE "))) req->method = HTTP_METHOD_DELETE;
|
||||||
} else if (s->len - s->cur >= 4
|
else if (consume_str(s, HTTP_STR("CONNECT "))) req->method = HTTP_METHOD_CONNECT;
|
||||||
&& s->src[s->cur+0] == 'P'
|
else if (consume_str(s, HTTP_STR("OPTIONS "))) req->method = HTTP_METHOD_OPTIONS;
|
||||||
&& s->src[s->cur+1] == 'O'
|
else if (consume_str(s, HTTP_STR("TRACE "))) req->method = HTTP_METHOD_TRACE;
|
||||||
&& s->src[s->cur+2] == 'S'
|
else if (consume_str(s, HTTP_STR("PATCH "))) req->method = HTTP_METHOD_PATCH;
|
||||||
&& s->src[s->cur+3] == 'T') {
|
else return -1;
|
||||||
s->cur += 4;
|
|
||||||
req->method = HTTP_METHOD_POST;
|
|
||||||
} else if (s->len - s->cur >= 3
|
|
||||||
&& s->src[s->cur+0] == 'P'
|
|
||||||
&& s->src[s->cur+1] == 'U'
|
|
||||||
&& s->src[s->cur+2] == 'T') {
|
|
||||||
s->cur += 3;
|
|
||||||
req->method = HTTP_METHOD_PUT;
|
|
||||||
} else if (s->len - s->cur >= 4
|
|
||||||
&& s->src[s->cur+0] == 'H'
|
|
||||||
&& s->src[s->cur+1] == 'E'
|
|
||||||
&& s->src[s->cur+2] == 'A'
|
|
||||||
&& s->src[s->cur+3] == 'D') {
|
|
||||||
s->cur += 4;
|
|
||||||
req->method = HTTP_METHOD_HEAD;
|
|
||||||
} else if (s->len - s->cur >= 6
|
|
||||||
&& s->src[s->cur+0] == 'D'
|
|
||||||
&& s->src[s->cur+1] == 'E'
|
|
||||||
&& s->src[s->cur+2] == 'L'
|
|
||||||
&& s->src[s->cur+3] == 'E'
|
|
||||||
&& s->src[s->cur+4] == 'T'
|
|
||||||
&& s->src[s->cur+5] == 'E') {
|
|
||||||
s->cur += 6;
|
|
||||||
req->method = HTTP_METHOD_DELETE;
|
|
||||||
} else if (s->len - s->cur >= 7
|
|
||||||
&& s->src[s->cur+0] == 'C'
|
|
||||||
&& s->src[s->cur+1] == 'O'
|
|
||||||
&& s->src[s->cur+2] == 'N'
|
|
||||||
&& s->src[s->cur+3] == 'N'
|
|
||||||
&& s->src[s->cur+4] == 'E'
|
|
||||||
&& s->src[s->cur+5] == 'C'
|
|
||||||
&& s->src[s->cur+6] == 'T') {
|
|
||||||
s->cur += 7;
|
|
||||||
req->method = HTTP_METHOD_CONNECT;
|
|
||||||
} else if (s->len - s->cur >= 7
|
|
||||||
&& s->src[s->cur+0] == 'O'
|
|
||||||
&& s->src[s->cur+1] == 'P'
|
|
||||||
&& s->src[s->cur+2] == 'T'
|
|
||||||
&& s->src[s->cur+3] == 'I'
|
|
||||||
&& s->src[s->cur+4] == 'O'
|
|
||||||
&& s->src[s->cur+5] == 'N'
|
|
||||||
&& s->src[s->cur+6] == 'S') {
|
|
||||||
s->cur += 7;
|
|
||||||
req->method = HTTP_METHOD_OPTIONS;
|
|
||||||
} else if (s->len - s->cur >= 5
|
|
||||||
&& s->src[s->cur+0] == 'T'
|
|
||||||
&& s->src[s->cur+1] == 'R'
|
|
||||||
&& s->src[s->cur+2] == 'A'
|
|
||||||
&& s->src[s->cur+3] == 'C'
|
|
||||||
&& s->src[s->cur+4] == 'E') {
|
|
||||||
s->cur += 5;
|
|
||||||
req->method = HTTP_METHOD_TRACE;
|
|
||||||
} else if (s->len - s->cur >= 5
|
|
||||||
&& s->src[s->cur+0] == 'P'
|
|
||||||
&& s->src[s->cur+1] == 'A'
|
|
||||||
&& s->src[s->cur+2] == 'T'
|
|
||||||
&& s->src[s->cur+3] == 'C'
|
|
||||||
&& s->src[s->cur+4] == 'H') {
|
|
||||||
s->cur += 5;
|
|
||||||
req->method = HTTP_METHOD_PATCH;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != ' ')
|
if (s->cur == s->len || s->src[s->cur] != ' ')
|
||||||
return -1;
|
return -1;
|
||||||
s->cur++;
|
s->cur++;
|
||||||
|
|
||||||
{
|
{
|
||||||
Scanner s2 = *s;
|
Scanner s2 = *s;
|
||||||
int peek = s->cur;
|
int peek = s->cur;
|
||||||
while (peek < s->len && s->src[peek] != ' ')
|
while (peek < s->len && s->src[peek] != ' ')
|
||||||
peek++;
|
peek++;
|
||||||
if (peek == s->len)
|
if (peek == s->len)
|
||||||
return -1;
|
return -1;
|
||||||
s2.len = peek;
|
s2.len = peek;
|
||||||
|
|
||||||
int ret = parse_request_target(&s2, &req->url);
|
int ret = parse_request_target(&s2, &req->url);
|
||||||
if (ret < 0) return ret;
|
if (ret < 0) return ret;
|
||||||
|
|
||||||
s->cur = s2.cur;
|
s->cur = s2.cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) {
|
||||||
if (s->len - s->cur < 7
|
req->minor = 1;
|
||||||
|| s->src[s->cur+0] != ' '
|
} else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) {
|
||||||
|| s->src[s->cur+1] != 'H'
|
req->minor = 0;
|
||||||
|| s->src[s->cur+2] != 'T'
|
} else {
|
||||||
|| s->src[s->cur+3] != 'T'
|
return -1;
|
||||||
|| s->src[s->cur+4] != 'P'
|
}
|
||||||
|| s->src[s->cur+5] != '/'
|
|
||||||
|| s->src[s->cur+6] != '1')
|
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 7;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '.')
|
int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS);
|
||||||
req->minor = 0;
|
if (num_headers < 0)
|
||||||
else {
|
return num_headers;
|
||||||
s->cur++;
|
req->num_headers = num_headers;
|
||||||
if (s->cur == s->len || !is_digit(s->src[s->cur]))
|
|
||||||
return -1; // ERROR;
|
|
||||||
req->minor = s->src[s->cur] - '0';
|
|
||||||
s->cur++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->len - s->cur < 2
|
bool body_expected = true;
|
||||||
|| s->src[s->cur+0] != '\r'
|
if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods?
|
||||||
|| s->src[s->cur+1] != '\n')
|
body_expected = false;
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS);
|
return parse_body(s, req->headers, req->num_headers, &req->body, body_expected);
|
||||||
if (num_headers < 0)
|
|
||||||
return num_headers;
|
|
||||||
req->num_headers = num_headers;
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
|
int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
|
||||||
@@ -1260,128 +1430,91 @@ static int parse_response(Scanner *s, HTTP_Response *res)
|
|||||||
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (s->len - s->cur < 6
|
if (consume_str(s, HTTP_STR("HTTP/1.1 "))) {
|
||||||
|| s->src[s->cur+0] != 'H'
|
res->minor = 1;
|
||||||
|| s->src[s->cur+1] != 'T'
|
} else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) {
|
||||||
|| s->src[s->cur+2] != 'T'
|
res->minor = 0;
|
||||||
|| s->src[s->cur+3] != 'P'
|
} else {
|
||||||
|| s->src[s->cur+4] != '/'
|
return -1;
|
||||||
|| s->src[s->cur+5] != '1')
|
}
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 6;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '.')
|
if (s->len - s->cur < 5
|
||||||
res->minor = 0;
|
|| s->src[s->cur+0] != ' '
|
||||||
else {
|
|| !is_digit(s->src[s->cur+1])
|
||||||
s->cur++;
|
|| !is_digit(s->src[s->cur+2])
|
||||||
if (s->cur == s->len || !is_digit(s->src[s->cur]))
|
|| !is_digit(s->src[s->cur+3])
|
||||||
return -1; // ERROR
|
|| s->src[s->cur+4] != ' ')
|
||||||
res->minor = s->src[s->cur] - '0';
|
return -1;
|
||||||
s->cur++;
|
s->cur += 5;
|
||||||
}
|
|
||||||
|
|
||||||
if (s->len - s->cur < 5
|
res->status =
|
||||||
|| s->src[s->cur+0] != ' '
|
(s->src[s->cur-2] - '0') * 1 +
|
||||||
|| !is_digit(s->src[s->cur+1])
|
(s->src[s->cur-3] - '0') * 10 +
|
||||||
|| !is_digit(s->src[s->cur+2])
|
(s->src[s->cur-4] - '0') * 100;
|
||||||
|| !is_digit(s->src[s->cur+3])
|
|
||||||
|| s->src[s->cur+4] != ' ')
|
|
||||||
return -1;
|
|
||||||
s->cur += 5;
|
|
||||||
|
|
||||||
res->status =
|
while (s->cur < s->len && (
|
||||||
(s->src[s->cur-2] - '0') * 1 +
|
s->src[s->cur] == '\t' ||
|
||||||
(s->src[s->cur-3] - '0') * 10 +
|
s->src[s->cur] == ' ' ||
|
||||||
(s->src[s->cur-4] - '0') * 100;
|
is_vchar(s->src[s->cur]))) // TODO: obs-text
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
while (s->cur < s->len && (
|
if (s->len - s->cur < 2
|
||||||
s->src[s->cur] == '\t' ||
|
|| s->src[s->cur+0] != '\r'
|
||||||
s->src[s->cur] == ' ' ||
|
|| s->src[s->cur+1] != '\n')
|
||||||
is_vchar(s->src[s->cur]))) // TODO: obs-text
|
return -1;
|
||||||
s->cur++;
|
s->cur += 2;
|
||||||
|
|
||||||
if (s->len - s->cur < 2
|
int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS);
|
||||||
|| s->src[s->cur+0] != '\r'
|
if (num_headers < 0)
|
||||||
|| s->src[s->cur+1] != '\n')
|
return num_headers;
|
||||||
return -1;
|
res->num_headers = num_headers;
|
||||||
s->cur += 2;
|
|
||||||
|
|
||||||
int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS);
|
bool body_expected = true; // TODO
|
||||||
if (num_headers < 0)
|
|
||||||
return num_headers;
|
|
||||||
res->num_headers = num_headers;
|
|
||||||
|
|
||||||
int content_length_index = http_find_header(
|
return parse_body(s, res->headers, res->num_headers, &res->body, body_expected);
|
||||||
res->headers, res->num_headers,
|
|
||||||
HTTP_STR("Content-Length"));
|
|
||||||
if (content_length_index == -1) {
|
|
||||||
res->body.ptr = NULL;
|
|
||||||
res->body.len = 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: transfer-encoding
|
|
||||||
|
|
||||||
HTTP_String content_length_str = res->headers[content_length_index].value;
|
|
||||||
|
|
||||||
unsigned long long content_length;
|
|
||||||
if (parse_content_length(content_length_str.ptr, content_length_str.len, &content_length) < 0) {
|
|
||||||
HTTP_ASSERT(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content_length > 1<<20) {
|
|
||||||
HTTP_ASSERT(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content_length > (unsigned long long) (s->len - s->cur))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
res->body.ptr = s->src + s->cur;
|
|
||||||
res->body.len = content_length;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
|
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_ipv4(&s, ipv4);
|
int ret = parse_ipv4(&s, ipv4);
|
||||||
if (ret < 0) return ret;
|
if (ret < 0) return ret;
|
||||||
return s.cur;
|
return s.cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
|
int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_ipv6(&s, ipv6);
|
int ret = parse_ipv6(&s, ipv6);
|
||||||
if (ret < 0) return ret;
|
if (ret < 0) return ret;
|
||||||
return s.cur;
|
return s.cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_url(char *src, int len, HTTP_URL *url)
|
int http_parse_url(char *src, int len, HTTP_URL *url)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_uri(&s, url, 1);
|
int ret = parse_uri(&s, url, 1);
|
||||||
if (ret == 1)
|
if (ret == 1)
|
||||||
return s.cur;
|
return s.cur;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_request(char *src, int len, HTTP_Request *req)
|
int http_parse_request(char *src, int len, HTTP_Request *req)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_request(&s, req);
|
int ret = parse_request(&s, req);
|
||||||
if (ret == 1)
|
if (ret == 1)
|
||||||
return s.cur;
|
return s.cur;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_response(char *src, int len, HTTP_Response *res)
|
int http_parse_response(char *src, int len, HTTP_Response *res)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_response(&s, res);
|
int ret = parse_response(&s, res);
|
||||||
if (ret == 1)
|
if (ret == 1)
|
||||||
return s.cur;
|
return s.cur;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -2418,7 +2551,7 @@ static EVP_PKEY *generate_rsa_key_pair(int key_bits)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EVP_PKEY *pkey;
|
EVP_PKEY *pkey = NULL;
|
||||||
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
|
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
|
||||||
EVP_PKEY_CTX_free(ctx);
|
EVP_PKEY_CTX_free(ctx);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -4420,6 +4553,9 @@ void http_request_builder_submit(HTTP_RequestBuilder handle)
|
|||||||
|
|
||||||
void http_response_free(HTTP_Response *res)
|
void http_response_free(HTTP_Response *res)
|
||||||
{
|
{
|
||||||
|
if (res == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
HTTP_Client *client = res->context;
|
HTTP_Client *client = res->context;
|
||||||
|
|
||||||
ClientConnection *conn = NULL;
|
ClientConnection *conn = NULL;
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#include <stddef.h>
|
||||||
|
#include <chttp.h>
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
http_global_init();
|
||||||
|
HTTP_Client *client = http_client_init();
|
||||||
|
if (client == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
HTTP_Response *responses[2];
|
||||||
|
|
||||||
|
HTTP_String urls[] = {
|
||||||
|
HTTP_STR("http://coz.is"),
|
||||||
|
HTTP_STR("http://coz.is"),
|
||||||
|
};
|
||||||
|
|
||||||
|
bool trace = false;
|
||||||
|
|
||||||
|
HTTP_RequestBuilder builder;
|
||||||
|
if (http_client_get_builder(client, &builder) < 0) return -1;
|
||||||
|
http_request_builder_trace(builder, trace);
|
||||||
|
http_request_builder_user_data(builder, &responses[0]);
|
||||||
|
http_request_builder_line(builder, HTTP_METHOD_GET, urls[0]);
|
||||||
|
http_request_builder_submit(builder);
|
||||||
|
|
||||||
|
if (http_client_get_builder(client, &builder) < 0) return -1;
|
||||||
|
http_request_builder_trace(builder, trace);
|
||||||
|
http_request_builder_user_data(builder, &responses[1]);
|
||||||
|
http_request_builder_line(builder, HTTP_METHOD_GET, urls[1]);
|
||||||
|
http_request_builder_submit(builder);
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
void **dst;
|
||||||
|
HTTP_Response *output;
|
||||||
|
if (http_client_wait(client, &output, (void*) &dst) < 0)
|
||||||
|
return -1;
|
||||||
|
*dst = output;
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTP_Response *responseA = responses[0];
|
||||||
|
HTTP_Response *responseB = responses[1];
|
||||||
|
|
||||||
|
// ... process responses ...
|
||||||
|
|
||||||
|
http_response_free(responseA);
|
||||||
|
http_response_free(responseB);
|
||||||
|
http_client_free(client);
|
||||||
|
http_global_free();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -158,6 +158,8 @@ int main(int argc, char **argv)
|
|||||||
// TODO
|
// TODO
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (res == NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
HTTP_String body = res->body;
|
HTTP_String body = res->body;
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1,10 +1,12 @@
|
|||||||
Allow resolving requests from different threads
|
Allow resolving requests from different threads
|
||||||
Find a way to make sure chttp.h and chttp.c are always up to date
|
Find a way to make sure chttp.h and chttp.c are always up to date
|
||||||
parse Transfer-Encoding
|
|
||||||
Find a way to compile OpenSSL on windows
|
Find a way to compile OpenSSL on windows
|
||||||
handle 3xx client redirections
|
handle 3xx client redirections
|
||||||
add discussion on string management
|
add discussion on string management
|
||||||
add discussion on error management
|
add discussion on error management
|
||||||
check that virtual hosts over HTTPS work
|
check that virtual hosts over HTTPS work
|
||||||
add examples for the router
|
add examples for the router
|
||||||
|
|
||||||
|
add timers
|
||||||
|
add debug and release builds
|
||||||
make it easy to switch on and off HTTPS when building
|
make it easy to switch on and off HTTPS when building
|
||||||
+1
-1
@@ -33,7 +33,7 @@ static EVP_PKEY *generate_rsa_key_pair(int key_bits)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
EVP_PKEY *pkey;
|
EVP_PKEY *pkey = NULL;
|
||||||
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
|
if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
|
||||||
EVP_PKEY_CTX_free(ctx);
|
EVP_PKEY_CTX_free(ctx);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -368,6 +368,9 @@ void http_request_builder_submit(HTTP_RequestBuilder handle)
|
|||||||
|
|
||||||
void http_response_free(HTTP_Response *res)
|
void http_response_free(HTTP_Response *res)
|
||||||
{
|
{
|
||||||
|
if (res == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
HTTP_Client *client = res->context;
|
HTTP_Client *client = res->context;
|
||||||
|
|
||||||
ClientConnection *conn = NULL;
|
ClientConnection *conn = NULL;
|
||||||
|
|||||||
+443
-310
@@ -2,6 +2,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
#ifndef HTTP_AMALGAMATION
|
#ifndef HTTP_AMALGAMATION
|
||||||
#include "parse.h"
|
#include "parse.h"
|
||||||
@@ -9,43 +11,43 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// From RFC 9112
|
// From RFC 9112
|
||||||
// request-target = origin-form
|
// request-target = origin-form
|
||||||
// / absolute-form
|
// / absolute-form
|
||||||
// / authority-form
|
// / authority-form
|
||||||
// / asterisk-form
|
// / asterisk-form
|
||||||
// origin-form = absolute-path [ "?" query ]
|
// origin-form = absolute-path [ "?" query ]
|
||||||
// absolute-form = absolute-URI
|
// absolute-form = absolute-URI
|
||||||
// authority-form = uri-host ":" port
|
// authority-form = uri-host ":" port
|
||||||
// asterisk-form = "*"
|
// asterisk-form = "*"
|
||||||
//
|
//
|
||||||
// From RFC 9110
|
// From RFC 9110
|
||||||
// URI-reference = <URI-reference, see [URI], Section 4.1>
|
// URI-reference = <URI-reference, see [URI], Section 4.1>
|
||||||
// absolute-URI = <absolute-URI, see [URI], Section 4.3>
|
// absolute-URI = <absolute-URI, see [URI], Section 4.3>
|
||||||
// relative-part = <relative-part, see [URI], Section 4.2>
|
// relative-part = <relative-part, see [URI], Section 4.2>
|
||||||
// authority = <authority, see [URI], Section 3.2>
|
// authority = <authority, see [URI], Section 3.2>
|
||||||
// uri-host = <host, see [URI], Section 3.2.2>
|
// uri-host = <host, see [URI], Section 3.2.2>
|
||||||
// port = <port, see [URI], Section 3.2.3>
|
// port = <port, see [URI], Section 3.2.3>
|
||||||
// path-abempty = <path-abempty, see [URI], Section 3.3>
|
// path-abempty = <path-abempty, see [URI], Section 3.3>
|
||||||
// segment = <segment, see [URI], Section 3.3>
|
// segment = <segment, see [URI], Section 3.3>
|
||||||
// query = <query, see [URI], Section 3.4>
|
// query = <query, see [URI], Section 3.4>
|
||||||
//
|
//
|
||||||
// absolute-path = 1*( "/" segment )
|
// absolute-path = 1*( "/" segment )
|
||||||
// partial-URI = relative-part [ "?" query ]
|
// partial-URI = relative-part [ "?" query ]
|
||||||
//
|
//
|
||||||
// From RFC 3986:
|
// From RFC 3986:
|
||||||
// segment = *pchar
|
// segment = *pchar
|
||||||
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||||
// pct-encoded = "%" HEXDIG HEXDIG
|
// pct-encoded = "%" HEXDIG HEXDIG
|
||||||
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
// / "*" / "+" / "," / ";" / "="
|
// / "*" / "+" / "," / ";" / "="
|
||||||
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||||
// query = *( pchar / "/" / "?" )
|
// query = *( pchar / "/" / "?" )
|
||||||
// absolute-URI = scheme ":" hier-part [ "?" query ]
|
// absolute-URI = scheme ":" hier-part [ "?" query ]
|
||||||
// hier-part = "//" authority path-abempty
|
// hier-part = "//" authority path-abempty
|
||||||
// / path-absolute
|
// / path-absolute
|
||||||
// / path-rootless
|
// / path-rootless
|
||||||
// / path-empty
|
// / path-empty
|
||||||
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *src;
|
char *src;
|
||||||
@@ -109,6 +111,10 @@ static int is_vchar(char c)
|
|||||||
return c >= ' ' && c <= '~';
|
return c >= ' ' && c <= '~';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CONSUME_OPTIONAL_SEQUENCE(scanner, func) \
|
||||||
|
while ((scanner)->cur < (scanner)->len && (func)((scanner)->src[(scanner)->cur])) \
|
||||||
|
(scanner)->cur++;
|
||||||
|
|
||||||
static int
|
static int
|
||||||
consume_absolute_path(Scanner *s)
|
consume_absolute_path(Scanner *s)
|
||||||
{
|
{
|
||||||
@@ -118,8 +124,7 @@ consume_absolute_path(Scanner *s)
|
|||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
while (s->cur < s->len && is_pchar(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar);
|
||||||
s->cur++;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '/')
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
||||||
break;
|
break;
|
||||||
@@ -156,8 +161,7 @@ static int parse_path(Scanner *s, HTTP_String *path, int abempty)
|
|||||||
s->cur++;
|
s->cur++;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
while (s->cur < s->len && is_pchar(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar);
|
||||||
s->cur++;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '/')
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
||||||
break;
|
break;
|
||||||
@@ -171,8 +175,7 @@ static int parse_path(Scanner *s, HTTP_String *path, int abempty)
|
|||||||
s->cur++;
|
s->cur++;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
|
||||||
while (s->cur < s->len && is_pchar(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_pchar)
|
||||||
s->cur++;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '/')
|
if (s->cur == s->len || s->src[s->cur] != '/')
|
||||||
break;
|
break;
|
||||||
@@ -452,8 +455,9 @@ static int parse_authority(Scanner *s, HTTP_Authority *authority)
|
|||||||
HTTP_String userinfo;
|
HTTP_String userinfo;
|
||||||
{
|
{
|
||||||
int start = s->cur;
|
int start = s->cur;
|
||||||
while (s->cur < s->len && is_userinfo(s->src[s->cur]))
|
|
||||||
s->cur++;
|
CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo);
|
||||||
|
|
||||||
if (s->cur < s->len && s->src[s->cur] == '@') {
|
if (s->cur < s->len && s->src[s->cur] == '@') {
|
||||||
userinfo = (HTTP_String) {
|
userinfo = (HTTP_String) {
|
||||||
s->src + start,
|
s->src + start,
|
||||||
@@ -651,6 +655,21 @@ static int parse_request_target(Scanner *s, HTTP_URL *url)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool consume_str(Scanner *scan, HTTP_String token)
|
||||||
|
{
|
||||||
|
HTTP_ASSERT(token.len > 0);
|
||||||
|
|
||||||
|
if (token.len > scan->len - scan->cur)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < token.len; i++)
|
||||||
|
if (scan->src[scan->cur + i] != token.ptr[i])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
scan->cur += token.len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static int is_header_body(char c)
|
static int is_header_body(char c)
|
||||||
{
|
{
|
||||||
return is_vchar(c) || c == ' ' || c == '\t';
|
return is_vchar(c) || c == ' ' || c == '\t';
|
||||||
@@ -659,16 +678,9 @@ static int is_header_body(char c)
|
|||||||
static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
||||||
{
|
{
|
||||||
int num_headers = 0;
|
int num_headers = 0;
|
||||||
for (;;) {
|
while (!consume_str(s, HTTP_STR("\r\n"))) {
|
||||||
|
|
||||||
if (s->len - s->cur > 1
|
// RFC 9112:
|
||||||
&& s->src[s->cur+0] == '\r'
|
|
||||||
&& s->src[s->cur+1] == '\n') {
|
|
||||||
s->cur += 2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 9112:
|
|
||||||
// field-line = field-name ":" OWS field-value OWS
|
// field-line = field-name ":" OWS field-value OWS
|
||||||
//
|
//
|
||||||
// RFC 9110:
|
// RFC 9110:
|
||||||
@@ -692,196 +704,354 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers)
|
|||||||
return -1; // ERROR
|
return -1; // ERROR
|
||||||
s->cur++;
|
s->cur++;
|
||||||
|
|
||||||
start = s->cur;
|
start = s->cur;
|
||||||
while (s->cur < s->len && is_header_body(s->src[s->cur]))
|
CONSUME_OPTIONAL_SEQUENCE(s, is_header_body);
|
||||||
s->cur++;
|
|
||||||
HTTP_String body = { s->src + start, s->cur - start };
|
HTTP_String body = { s->src + start, s->cur - start };
|
||||||
body = http_trim(body);
|
body = http_trim(body);
|
||||||
|
|
||||||
if (s->len - s->cur < 2
|
if (num_headers < max_headers)
|
||||||
|| s->src[s->cur+0] != '\r'
|
headers[num_headers++] = (HTTP_Header) { name, body };
|
||||||
|| s->src[s->cur+1] != '\n')
|
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 2;
|
|
||||||
|
|
||||||
if (num_headers < max_headers)
|
if (!consume_str(s, HTTP_STR("\r\n"))) {
|
||||||
headers[num_headers++] = (HTTP_Header) { name, body };
|
return -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return num_headers;
|
return num_headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TRANSFER_ENCODING_OPTION_CHUNKED,
|
||||||
|
TRANSFER_ENCODING_OPTION_COMPRESS,
|
||||||
|
TRANSFER_ENCODING_OPTION_DEFLATE,
|
||||||
|
TRANSFER_ENCODING_OPTION_GZIP,
|
||||||
|
} TransferEncodingOption;
|
||||||
|
|
||||||
|
static bool is_space(char c)
|
||||||
|
{
|
||||||
|
return c == ' ' || c == '\t';
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parse_content_length(const char *src, int len, unsigned long long *out)
|
parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max)
|
||||||
{
|
{
|
||||||
int cur = 0;
|
Scanner s = { src.ptr, src.len, 0 };
|
||||||
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
|
|
||||||
cur++;
|
|
||||||
|
|
||||||
if (cur == len || !is_digit(src[cur]))
|
int num = 0;
|
||||||
return -1;
|
for (;;) {
|
||||||
|
|
||||||
unsigned long long buf = 0;
|
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
||||||
do {
|
|
||||||
int d = src[cur++] - '0';
|
|
||||||
if (buf > (UINT64_MAX - d) / 10)
|
|
||||||
return -1;
|
|
||||||
buf = buf * 10 + d;
|
|
||||||
} while (cur < len && is_digit(src[cur]));
|
|
||||||
|
|
||||||
*out = buf;
|
TransferEncodingOption opt;
|
||||||
return 0;
|
if (0) {}
|
||||||
|
else if (consume_str(&s, HTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED;
|
||||||
|
else if (consume_str(&s, HTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS;
|
||||||
|
else if (consume_str(&s, HTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE;
|
||||||
|
else if (consume_str(&s, HTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP;
|
||||||
|
else return -1; // Invalid option
|
||||||
|
|
||||||
|
if (num == max)
|
||||||
|
return -1; // Too many options
|
||||||
|
dst[num++] = opt;
|
||||||
|
|
||||||
|
CONSUME_OPTIONAL_SEQUENCE(&s, is_space);
|
||||||
|
|
||||||
|
if (s.cur == s.len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (s.src[s.cur] != ',')
|
||||||
|
return -1; // Missing comma separator
|
||||||
|
}
|
||||||
|
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_content_length(const char *src, int len, uint64_t *out)
|
||||||
|
{
|
||||||
|
int cur = 0;
|
||||||
|
while (cur < len && (src[cur] == ' ' || src[cur] == '\t'))
|
||||||
|
cur++;
|
||||||
|
|
||||||
|
if (cur == len || !is_digit(src[cur]))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
uint64_t buf = 0;
|
||||||
|
do {
|
||||||
|
int d = src[cur++] - '0';
|
||||||
|
if (buf > (UINT64_MAX - d) / 10)
|
||||||
|
return -1;
|
||||||
|
buf = buf * 10 + d;
|
||||||
|
} while (cur < len && is_digit(src[cur]));
|
||||||
|
|
||||||
|
*out = buf;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_body(Scanner *s,
|
||||||
|
HTTP_Header *headers, int num_headers,
|
||||||
|
HTTP_String *body, bool body_expected)
|
||||||
|
{
|
||||||
|
|
||||||
|
// RFC 9112 section 6:
|
||||||
|
// The presence of a message body in a request is signaled by a Content-Length or
|
||||||
|
// Transfer-Encoding header field. Request message framing is independent of method
|
||||||
|
// semantics.
|
||||||
|
|
||||||
|
int header_index = http_find_header(headers, num_headers, HTTP_STR("Transfer-Encoding"));
|
||||||
|
if (header_index != -1) {
|
||||||
|
|
||||||
|
// RFC 9112 section 6.1:
|
||||||
|
// A server MAY reject a request that contains both Content-Length and Transfer-Encoding
|
||||||
|
// or process such a request in accordance with the Transfer-Encoding alone. Regardless,
|
||||||
|
// the server MUST close the connection after responding to such a request to avoid the
|
||||||
|
// potential attacks.
|
||||||
|
if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
HTTP_String value = headers[header_index].value;
|
||||||
|
|
||||||
|
// RFC 9112 section 6.1:
|
||||||
|
// If any transfer coding other than chunked is applied to a request's content, the
|
||||||
|
// sender MUST apply chunked as the final transfer coding to ensure that the message
|
||||||
|
// is properly framed. If any transfer coding other than chunked is applied to a
|
||||||
|
// response's content, the sender MUST either apply chunked as the final transfer
|
||||||
|
// coding or terminate the message by closing the connection.
|
||||||
|
|
||||||
|
TransferEncodingOption opts[8];
|
||||||
|
int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts));
|
||||||
|
if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
HTTP_String chunks_maybe[128];
|
||||||
|
HTTP_String *chunks = chunks_maybe;
|
||||||
|
int num_chunks = 0;
|
||||||
|
int max_chunks = HTTP_COUNT(chunks_maybe);
|
||||||
|
|
||||||
|
#define FREE_CHUNK_LIST \
|
||||||
|
if (chunks != chunks_maybe) \
|
||||||
|
free(chunks);
|
||||||
|
|
||||||
|
char *content_start = s->src + s->cur;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
|
||||||
|
// RFC 9112 section 7.1:
|
||||||
|
// The chunked transfer coding wraps content in order to transfer it as a series of chunks,
|
||||||
|
// each with its own size indicator, followed by an OPTIONAL trailer section containing
|
||||||
|
// trailer fields.
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_hex_digit(s->src[s->cur])) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int chunk_len = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
char c = s->src[s->cur++];
|
||||||
|
int n = hex_digit_to_int(c);
|
||||||
|
if (chunk_len > (INT_MAX - n) / 16) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1; // overflow
|
||||||
|
}
|
||||||
|
chunk_len = chunk_len * 16 + n;
|
||||||
|
} while (s->cur < s->len && is_hex_digit(s->src[s->cur]));
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
if (s->src[s->cur] != '\r') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (s->src[s->cur] != '\n') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
char *chunk_ptr = s->src + s->cur;
|
||||||
|
|
||||||
|
if (chunk_len > s->len - s->cur) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
s->cur += chunk_len;
|
||||||
|
|
||||||
|
if (s->cur == s->len)
|
||||||
|
return 0; // Incomplete request
|
||||||
|
if (s->src[s->cur] != '\r') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
if (s->cur == s->len) {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return 0; // Incomplete request
|
||||||
|
}
|
||||||
|
if (s->src[s->cur] != '\n') {
|
||||||
|
FREE_CHUNK_LIST
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
|
if (chunk_len == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (num_chunks == max_chunks) {
|
||||||
|
|
||||||
|
max_chunks *= 2;
|
||||||
|
|
||||||
|
HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_String));
|
||||||
|
if (new_chunks == NULL) {
|
||||||
|
if (chunks != chunks_maybe)
|
||||||
|
free(chunks);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_chunks; i++)
|
||||||
|
new_chunks[i] = chunks[i];
|
||||||
|
|
||||||
|
if (chunks != chunks_maybe)
|
||||||
|
free(chunks);
|
||||||
|
|
||||||
|
chunks = new_chunks;
|
||||||
|
}
|
||||||
|
chunks[num_chunks++] = (HTTP_String) { chunk_ptr, chunk_len };
|
||||||
|
}
|
||||||
|
|
||||||
|
char *content_ptr = content_start;
|
||||||
|
for (int i = 0; i < num_chunks; i++) {
|
||||||
|
memmove(content_ptr, chunks[i].ptr, chunks[i].len);
|
||||||
|
content_ptr += chunks[i].len;
|
||||||
|
}
|
||||||
|
|
||||||
|
*body = (HTTP_String) {
|
||||||
|
content_start,
|
||||||
|
content_ptr - content_start
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chunks != chunks_maybe)
|
||||||
|
free(chunks);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 9112 section 6.3:
|
||||||
|
// If a valid Content-Length header field is present without Transfer-Encoding,
|
||||||
|
// its decimal value defines the expected message body length in octets.
|
||||||
|
|
||||||
|
header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length"));
|
||||||
|
if (header_index != -1) {
|
||||||
|
|
||||||
|
// Have Content-Length
|
||||||
|
HTTP_String value = headers[header_index].value;
|
||||||
|
|
||||||
|
uint64_t tmp;
|
||||||
|
if (parse_content_length(value.ptr, value.len, &tmp) < 0)
|
||||||
|
return -1;
|
||||||
|
if (tmp > INT_MAX)
|
||||||
|
return -1;
|
||||||
|
int len = (int) tmp;
|
||||||
|
|
||||||
|
if (len > s->len - s->cur)
|
||||||
|
return 0; // Incomplete request
|
||||||
|
|
||||||
|
*body = (HTTP_String) { s->src + s->cur, len };
|
||||||
|
|
||||||
|
s->cur += len;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No Content-Length or Transfer-Encoding
|
||||||
|
if (body_expected) return -1;
|
||||||
|
|
||||||
|
*body = (HTTP_String) { NULL, 0 };
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int contains_head(char *src, int len)
|
static int contains_head(char *src, int len)
|
||||||
{
|
{
|
||||||
int cur = 0;
|
int cur = 0;
|
||||||
while (len - cur > 3) {
|
while (len - cur > 3) {
|
||||||
if (src[cur+0] == '\r' &&
|
if (src[cur+0] == '\r' &&
|
||||||
src[cur+1] == '\n' &&
|
src[cur+1] == '\n' &&
|
||||||
src[cur+2] == '\r' &&
|
src[cur+2] == '\r' &&
|
||||||
src[cur+3] == '\n')
|
src[cur+3] == '\n')
|
||||||
return 1;
|
return 1;
|
||||||
cur++;
|
cur++;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_request(Scanner *s, HTTP_Request *req)
|
static int parse_request(Scanner *s, HTTP_Request *req)
|
||||||
{
|
{
|
||||||
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (s->len - s->cur >= 3
|
if (0) {}
|
||||||
&& s->src[s->cur+0] == 'G'
|
else if (consume_str(s, HTTP_STR("GET "))) req->method = HTTP_METHOD_GET;
|
||||||
&& s->src[s->cur+1] == 'E'
|
else if (consume_str(s, HTTP_STR("POST "))) req->method = HTTP_METHOD_POST;
|
||||||
&& s->src[s->cur+2] == 'T') {
|
else if (consume_str(s, HTTP_STR("PUT "))) req->method = HTTP_METHOD_PUT;
|
||||||
s->cur += 3;
|
else if (consume_str(s, HTTP_STR("HEAD "))) req->method = HTTP_METHOD_HEAD;
|
||||||
req->method = HTTP_METHOD_GET;
|
else if (consume_str(s, HTTP_STR("DELETE "))) req->method = HTTP_METHOD_DELETE;
|
||||||
} else if (s->len - s->cur >= 4
|
else if (consume_str(s, HTTP_STR("CONNECT "))) req->method = HTTP_METHOD_CONNECT;
|
||||||
&& s->src[s->cur+0] == 'P'
|
else if (consume_str(s, HTTP_STR("OPTIONS "))) req->method = HTTP_METHOD_OPTIONS;
|
||||||
&& s->src[s->cur+1] == 'O'
|
else if (consume_str(s, HTTP_STR("TRACE "))) req->method = HTTP_METHOD_TRACE;
|
||||||
&& s->src[s->cur+2] == 'S'
|
else if (consume_str(s, HTTP_STR("PATCH "))) req->method = HTTP_METHOD_PATCH;
|
||||||
&& s->src[s->cur+3] == 'T') {
|
else return -1;
|
||||||
s->cur += 4;
|
|
||||||
req->method = HTTP_METHOD_POST;
|
|
||||||
} else if (s->len - s->cur >= 3
|
|
||||||
&& s->src[s->cur+0] == 'P'
|
|
||||||
&& s->src[s->cur+1] == 'U'
|
|
||||||
&& s->src[s->cur+2] == 'T') {
|
|
||||||
s->cur += 3;
|
|
||||||
req->method = HTTP_METHOD_PUT;
|
|
||||||
} else if (s->len - s->cur >= 4
|
|
||||||
&& s->src[s->cur+0] == 'H'
|
|
||||||
&& s->src[s->cur+1] == 'E'
|
|
||||||
&& s->src[s->cur+2] == 'A'
|
|
||||||
&& s->src[s->cur+3] == 'D') {
|
|
||||||
s->cur += 4;
|
|
||||||
req->method = HTTP_METHOD_HEAD;
|
|
||||||
} else if (s->len - s->cur >= 6
|
|
||||||
&& s->src[s->cur+0] == 'D'
|
|
||||||
&& s->src[s->cur+1] == 'E'
|
|
||||||
&& s->src[s->cur+2] == 'L'
|
|
||||||
&& s->src[s->cur+3] == 'E'
|
|
||||||
&& s->src[s->cur+4] == 'T'
|
|
||||||
&& s->src[s->cur+5] == 'E') {
|
|
||||||
s->cur += 6;
|
|
||||||
req->method = HTTP_METHOD_DELETE;
|
|
||||||
} else if (s->len - s->cur >= 7
|
|
||||||
&& s->src[s->cur+0] == 'C'
|
|
||||||
&& s->src[s->cur+1] == 'O'
|
|
||||||
&& s->src[s->cur+2] == 'N'
|
|
||||||
&& s->src[s->cur+3] == 'N'
|
|
||||||
&& s->src[s->cur+4] == 'E'
|
|
||||||
&& s->src[s->cur+5] == 'C'
|
|
||||||
&& s->src[s->cur+6] == 'T') {
|
|
||||||
s->cur += 7;
|
|
||||||
req->method = HTTP_METHOD_CONNECT;
|
|
||||||
} else if (s->len - s->cur >= 7
|
|
||||||
&& s->src[s->cur+0] == 'O'
|
|
||||||
&& s->src[s->cur+1] == 'P'
|
|
||||||
&& s->src[s->cur+2] == 'T'
|
|
||||||
&& s->src[s->cur+3] == 'I'
|
|
||||||
&& s->src[s->cur+4] == 'O'
|
|
||||||
&& s->src[s->cur+5] == 'N'
|
|
||||||
&& s->src[s->cur+6] == 'S') {
|
|
||||||
s->cur += 7;
|
|
||||||
req->method = HTTP_METHOD_OPTIONS;
|
|
||||||
} else if (s->len - s->cur >= 5
|
|
||||||
&& s->src[s->cur+0] == 'T'
|
|
||||||
&& s->src[s->cur+1] == 'R'
|
|
||||||
&& s->src[s->cur+2] == 'A'
|
|
||||||
&& s->src[s->cur+3] == 'C'
|
|
||||||
&& s->src[s->cur+4] == 'E') {
|
|
||||||
s->cur += 5;
|
|
||||||
req->method = HTTP_METHOD_TRACE;
|
|
||||||
} else if (s->len - s->cur >= 5
|
|
||||||
&& s->src[s->cur+0] == 'P'
|
|
||||||
&& s->src[s->cur+1] == 'A'
|
|
||||||
&& s->src[s->cur+2] == 'T'
|
|
||||||
&& s->src[s->cur+3] == 'C'
|
|
||||||
&& s->src[s->cur+4] == 'H') {
|
|
||||||
s->cur += 5;
|
|
||||||
req->method = HTTP_METHOD_PATCH;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != ' ')
|
if (s->cur == s->len || s->src[s->cur] != ' ')
|
||||||
return -1;
|
return -1;
|
||||||
s->cur++;
|
s->cur++;
|
||||||
|
|
||||||
{
|
{
|
||||||
Scanner s2 = *s;
|
Scanner s2 = *s;
|
||||||
int peek = s->cur;
|
int peek = s->cur;
|
||||||
while (peek < s->len && s->src[peek] != ' ')
|
while (peek < s->len && s->src[peek] != ' ')
|
||||||
peek++;
|
peek++;
|
||||||
if (peek == s->len)
|
if (peek == s->len)
|
||||||
return -1;
|
return -1;
|
||||||
s2.len = peek;
|
s2.len = peek;
|
||||||
|
|
||||||
int ret = parse_request_target(&s2, &req->url);
|
int ret = parse_request_target(&s2, &req->url);
|
||||||
if (ret < 0) return ret;
|
if (ret < 0) return ret;
|
||||||
|
|
||||||
s->cur = s2.cur;
|
s->cur = s2.cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) {
|
||||||
if (s->len - s->cur < 7
|
req->minor = 1;
|
||||||
|| s->src[s->cur+0] != ' '
|
} else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) {
|
||||||
|| s->src[s->cur+1] != 'H'
|
req->minor = 0;
|
||||||
|| s->src[s->cur+2] != 'T'
|
} else {
|
||||||
|| s->src[s->cur+3] != 'T'
|
return -1;
|
||||||
|| s->src[s->cur+4] != 'P'
|
}
|
||||||
|| s->src[s->cur+5] != '/'
|
|
||||||
|| s->src[s->cur+6] != '1')
|
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 7;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '.')
|
int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS);
|
||||||
req->minor = 0;
|
if (num_headers < 0)
|
||||||
else {
|
return num_headers;
|
||||||
s->cur++;
|
req->num_headers = num_headers;
|
||||||
if (s->cur == s->len || !is_digit(s->src[s->cur]))
|
|
||||||
return -1; // ERROR;
|
|
||||||
req->minor = s->src[s->cur] - '0';
|
|
||||||
s->cur++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s->len - s->cur < 2
|
bool body_expected = true;
|
||||||
|| s->src[s->cur+0] != '\r'
|
if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods?
|
||||||
|| s->src[s->cur+1] != '\n')
|
body_expected = false;
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS);
|
return parse_body(s, req->headers, req->num_headers, &req->body, body_expected);
|
||||||
if (num_headers < 0)
|
|
||||||
return num_headers;
|
|
||||||
req->num_headers = num_headers;
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
|
int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name)
|
||||||
@@ -897,126 +1067,89 @@ static int parse_response(Scanner *s, HTTP_Response *res)
|
|||||||
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
if (!contains_head(s->src + s->cur, s->len - s->cur))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (s->len - s->cur < 6
|
if (consume_str(s, HTTP_STR("HTTP/1.1 "))) {
|
||||||
|| s->src[s->cur+0] != 'H'
|
res->minor = 1;
|
||||||
|| s->src[s->cur+1] != 'T'
|
} else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) {
|
||||||
|| s->src[s->cur+2] != 'T'
|
res->minor = 0;
|
||||||
|| s->src[s->cur+3] != 'P'
|
} else {
|
||||||
|| s->src[s->cur+4] != '/'
|
return -1;
|
||||||
|| s->src[s->cur+5] != '1')
|
}
|
||||||
return -1; // ERROR
|
|
||||||
s->cur += 6;
|
|
||||||
|
|
||||||
if (s->cur == s->len || s->src[s->cur] != '.')
|
if (s->len - s->cur < 5
|
||||||
res->minor = 0;
|
|| s->src[s->cur+0] != ' '
|
||||||
else {
|
|| !is_digit(s->src[s->cur+1])
|
||||||
s->cur++;
|
|| !is_digit(s->src[s->cur+2])
|
||||||
if (s->cur == s->len || !is_digit(s->src[s->cur]))
|
|| !is_digit(s->src[s->cur+3])
|
||||||
return -1; // ERROR
|
|| s->src[s->cur+4] != ' ')
|
||||||
res->minor = s->src[s->cur] - '0';
|
return -1;
|
||||||
s->cur++;
|
s->cur += 5;
|
||||||
}
|
|
||||||
|
|
||||||
if (s->len - s->cur < 5
|
res->status =
|
||||||
|| s->src[s->cur+0] != ' '
|
(s->src[s->cur-2] - '0') * 1 +
|
||||||
|| !is_digit(s->src[s->cur+1])
|
(s->src[s->cur-3] - '0') * 10 +
|
||||||
|| !is_digit(s->src[s->cur+2])
|
(s->src[s->cur-4] - '0') * 100;
|
||||||
|| !is_digit(s->src[s->cur+3])
|
|
||||||
|| s->src[s->cur+4] != ' ')
|
|
||||||
return -1;
|
|
||||||
s->cur += 5;
|
|
||||||
|
|
||||||
res->status =
|
while (s->cur < s->len && (
|
||||||
(s->src[s->cur-2] - '0') * 1 +
|
s->src[s->cur] == '\t' ||
|
||||||
(s->src[s->cur-3] - '0') * 10 +
|
s->src[s->cur] == ' ' ||
|
||||||
(s->src[s->cur-4] - '0') * 100;
|
is_vchar(s->src[s->cur]))) // TODO: obs-text
|
||||||
|
s->cur++;
|
||||||
|
|
||||||
while (s->cur < s->len && (
|
if (s->len - s->cur < 2
|
||||||
s->src[s->cur] == '\t' ||
|
|| s->src[s->cur+0] != '\r'
|
||||||
s->src[s->cur] == ' ' ||
|
|| s->src[s->cur+1] != '\n')
|
||||||
is_vchar(s->src[s->cur]))) // TODO: obs-text
|
return -1;
|
||||||
s->cur++;
|
s->cur += 2;
|
||||||
|
|
||||||
if (s->len - s->cur < 2
|
int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS);
|
||||||
|| s->src[s->cur+0] != '\r'
|
if (num_headers < 0)
|
||||||
|| s->src[s->cur+1] != '\n')
|
return num_headers;
|
||||||
return -1;
|
res->num_headers = num_headers;
|
||||||
s->cur += 2;
|
|
||||||
|
|
||||||
int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS);
|
bool body_expected = true; // TODO
|
||||||
if (num_headers < 0)
|
|
||||||
return num_headers;
|
|
||||||
res->num_headers = num_headers;
|
|
||||||
|
|
||||||
int content_length_index = http_find_header(
|
return parse_body(s, res->headers, res->num_headers, &res->body, body_expected);
|
||||||
res->headers, res->num_headers,
|
|
||||||
HTTP_STR("Content-Length"));
|
|
||||||
if (content_length_index == -1) {
|
|
||||||
res->body.ptr = NULL;
|
|
||||||
res->body.len = 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: transfer-encoding
|
|
||||||
|
|
||||||
HTTP_String content_length_str = res->headers[content_length_index].value;
|
|
||||||
|
|
||||||
unsigned long long content_length;
|
|
||||||
if (parse_content_length(content_length_str.ptr, content_length_str.len, &content_length) < 0) {
|
|
||||||
HTTP_ASSERT(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content_length > 1<<20) {
|
|
||||||
HTTP_ASSERT(0); // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content_length > (unsigned long long) (s->len - s->cur))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
res->body.ptr = s->src + s->cur;
|
|
||||||
res->body.len = content_length;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
|
int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_ipv4(&s, ipv4);
|
int ret = parse_ipv4(&s, ipv4);
|
||||||
if (ret < 0) return ret;
|
if (ret < 0) return ret;
|
||||||
return s.cur;
|
return s.cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
|
int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_ipv6(&s, ipv6);
|
int ret = parse_ipv6(&s, ipv6);
|
||||||
if (ret < 0) return ret;
|
if (ret < 0) return ret;
|
||||||
return s.cur;
|
return s.cur;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_url(char *src, int len, HTTP_URL *url)
|
int http_parse_url(char *src, int len, HTTP_URL *url)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_uri(&s, url, 1);
|
int ret = parse_uri(&s, url, 1);
|
||||||
if (ret == 1)
|
if (ret == 1)
|
||||||
return s.cur;
|
return s.cur;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_request(char *src, int len, HTTP_Request *req)
|
int http_parse_request(char *src, int len, HTTP_Request *req)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_request(&s, req);
|
int ret = parse_request(&s, req);
|
||||||
if (ret == 1)
|
if (ret == 1)
|
||||||
return s.cur;
|
return s.cur;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int http_parse_response(char *src, int len, HTTP_Response *res)
|
int http_parse_response(char *src, int len, HTTP_Response *res)
|
||||||
{
|
{
|
||||||
Scanner s = {src, len, 0};
|
Scanner s = {src, len, 0};
|
||||||
int ret = parse_response(&s, res);
|
int ret = parse_response(&s, res);
|
||||||
if (ret == 1)
|
if (ret == 1)
|
||||||
return s.cur;
|
return s.cur;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user