560 lines
14 KiB
C
560 lines
14 KiB
C
#include "include/conf.h"
|
|
|
|
#include <vessel/def.h>
|
|
#include <vessel/hmap.h>
|
|
#include <vessel/stream.h>
|
|
#include <vessel/vessel.h>
|
|
|
|
#include "include/request.h"
|
|
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
static bool vw_is_http_path_char_valid(const char chr) {
|
|
if (isalnum(chr)) {
|
|
return true;
|
|
}
|
|
|
|
switch (chr) {
|
|
case '-':
|
|
case '_':
|
|
case '.':
|
|
case '~':
|
|
case '/':
|
|
case '?':
|
|
case '&':
|
|
case '=':
|
|
case '%':
|
|
case '+':
|
|
case ':':
|
|
case '@':
|
|
case '!':
|
|
case '$':
|
|
case ',':
|
|
case ';':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool vw_HTTPRequest_init(vw_HTTPRequest *http, vs_Stream *stream) {
|
|
if (!http || !stream) {
|
|
return false;
|
|
}
|
|
|
|
http->stream = stream;
|
|
http->keepalive = false;
|
|
|
|
http->res.code = 200;
|
|
http->res.began = false;
|
|
http->res.headered = false;
|
|
|
|
if (!vs_HMap_init(&http->headers)) {
|
|
return false;
|
|
}
|
|
|
|
if (!vs_HMap_init(&http->res.headers_buf)) {
|
|
vs_HMap_destroy_free(&http->headers);
|
|
return false;
|
|
}
|
|
|
|
if (!vw_HTTPBody_init(&http->body, stream, 0, false)) {
|
|
vs_HMap_destroy_free(&http->headers);
|
|
vs_HMap_destroy_free(&http->res.headers_buf);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static vw_HTTPRequestParseError vw_parse_method(const char *buf, char *method, size_t *idx) {
|
|
size_t jdx = 0;
|
|
|
|
if (!isupper(buf[*idx])) {
|
|
return vw_HTTPRequestParseError_format_method;
|
|
}
|
|
|
|
while (jdx < VW_HTTP_METHOD_MAX_LENGTH && isupper(buf[*idx])) {
|
|
method[jdx++] = buf[(*idx)++];
|
|
}
|
|
|
|
method[jdx] = '\0';
|
|
return vw_HTTPRequestParseError_ok;
|
|
}
|
|
|
|
static vw_HTTPRequestParseError vw_parse_path(const char *buf, char *path, size_t *idx) {
|
|
size_t jdx = 0;
|
|
|
|
/* NOTE: We just normalise it at Path_parse(). */
|
|
/* if (buf[*idx] != '/') */
|
|
/* return HTTPRequestParseError_format_path; */
|
|
|
|
while (jdx < VW_HTTP_PATH_MAX_LENGTH && vw_is_http_path_char_valid(buf[*idx])) {
|
|
path[jdx++] = buf[(*idx)++];
|
|
}
|
|
|
|
path[jdx] = '\0';
|
|
vs_lowstr(path);
|
|
return vw_HTTPRequestParseError_ok;
|
|
}
|
|
|
|
static vw_HTTPRequestParseError vw_parse_version_and_headers(
|
|
char *buf, size_t *idx, char *http_version, vs_HMap *headers, size_t size) {
|
|
if (strncmp(buf + *idx, "HTTP/1.1", VW_HTTP_VERSION_LENGTH) == 0 ||
|
|
strncmp(buf + *idx, "HTTP/1.0", VW_HTTP_VERSION_LENGTH) == 0) {
|
|
strncpy(http_version, buf + *idx, VW_HTTP_VERSION_LENGTH);
|
|
http_version[VW_HTTP_VERSION_LENGTH] = '\0';
|
|
} else {
|
|
return vw_HTTPRequestParseError_http_version;
|
|
}
|
|
|
|
*idx += VW_HTTP_VERSION_LENGTH;
|
|
|
|
if (strncmp(buf + *idx, VW_HTTP_CRLF, VW_HTTP_CRLF_LENGTH) != 0) {
|
|
return vw_HTTPRequestParseError_format;
|
|
}
|
|
|
|
*idx += VW_HTTP_CRLF_LENGTH;
|
|
|
|
if (!vw_HTTP_parse_headers(buf, size, headers, idx)) {
|
|
return vw_HTTPRequestParseError_format_header;
|
|
}
|
|
|
|
return vw_HTTPRequestParseError_ok;
|
|
}
|
|
|
|
vw_HTTPRequestParseError vw_HTTPRequest_read(vw_HTTPRequest *http) {
|
|
size_t size = 0;
|
|
|
|
bool found = false;
|
|
size_t idx = 0;
|
|
|
|
vw_HTTPRequestParseError error;
|
|
|
|
if (!http) {
|
|
return vw_HTTPRequestParseError_input;
|
|
}
|
|
|
|
size = vs_Stream_readb(http->stream,
|
|
http->_buf,
|
|
VW_HTTP_BUFFER_MAX_SIZE,
|
|
VW_HTTP_2_CRLF,
|
|
VW_HTTP_2_CRLF_LENGTH,
|
|
&found,
|
|
true); /* This quits early if a NULL byte is detected in the headers */
|
|
|
|
if (vs_Stream_ignore_err(size) < VW_HTTP_SMALLEST_REQUEST) {
|
|
return vw_HTTPRequestParseError_read;
|
|
}
|
|
|
|
if (!vw_HTTP_validate_request_headers(http->_buf, size)) {
|
|
return vw_HTTPRequestParseError_format;
|
|
}
|
|
|
|
/* Parse the buffer. */
|
|
|
|
http->header_size = size;
|
|
|
|
if ((error = vw_parse_method(http->_buf, http->method, &idx)) != vw_HTTPRequestParseError_ok) {
|
|
return error;
|
|
}
|
|
|
|
if (http->_buf[idx++] != ' ') {
|
|
return vw_HTTPRequestParseError_format_sp;
|
|
}
|
|
|
|
if ((error = vw_parse_path(http->_buf, http->path, &idx)) != vw_HTTPRequestParseError_ok) {
|
|
return error;
|
|
}
|
|
|
|
if (http->_buf[idx++] != ' ') {
|
|
return vw_HTTPRequestParseError_format_sp;
|
|
}
|
|
|
|
if ((error =
|
|
vw_parse_version_and_headers(http->_buf, &idx, http->version, &http->headers, size)) !=
|
|
vw_HTTPRequestParseError_ok) {
|
|
return error;
|
|
}
|
|
|
|
/* Connection header */
|
|
|
|
const vs_HMapEntry *conn = vs_HMap_find(&http->headers, "connection");
|
|
|
|
if (conn) {
|
|
http->keepalive = (strcmp(conn->value, "keep-alive") == 0);
|
|
} else {
|
|
http->keepalive = (strcmp("HTTP/1.1", http->version) == 0);
|
|
}
|
|
|
|
/* Chunked header */
|
|
|
|
const vs_HMapEntry *tenc = vs_HMap_find(&http->headers, "transfer-encoding");
|
|
bool tenc_res = false;
|
|
|
|
if (tenc) {
|
|
tenc_res = vw_HTTPBody_adjust_chunkiness(&http->body, true, 0);
|
|
} else {
|
|
const vs_HMapEntry *len = vs_HMap_find(&http->headers, "content-length");
|
|
|
|
if (len) {
|
|
tenc_res = vw_HTTPBody_adjust_chunkiness(&http->body, false, vs_str2usize(len->value));
|
|
} else {
|
|
tenc_res = vw_HTTPBody_adjust_chunkiness(&http->body, false, 0);
|
|
}
|
|
}
|
|
|
|
return tenc_res ? vw_HTTPRequestParseError_ok : vw_HTTPRequestParseError_content_chunkiness;
|
|
}
|
|
|
|
const char *vw_HTTPRequestParseError_to_str(vw_HTTPRequestParseError err) {
|
|
switch (err) {
|
|
case vw_HTTPRequestParseError_ok:
|
|
return "OK";
|
|
case vw_HTTPRequestParseError_read:
|
|
return "Stream read error";
|
|
case vw_HTTPRequestParseError_format:
|
|
return "Format error";
|
|
case vw_HTTPRequestParseError_format_method:
|
|
return "Improperly formatted method";
|
|
case vw_HTTPRequestParseError_format_sp:
|
|
return "Improperly formatted separator";
|
|
case vw_HTTPRequestParseError_format_path:
|
|
return "Improperly formatted path";
|
|
case vw_HTTPRequestParseError_http_version:
|
|
return "Unsuported HTTP version";
|
|
case vw_HTTPRequestParseError_format_header:
|
|
return "Improperly formatted header";
|
|
case vw_HTTPRequestParseError_input:
|
|
return "Invalid input";
|
|
case vw_HTTPRequestParseError_content_length:
|
|
return "Content length required";
|
|
case vw_HTTPRequestParseError_content_chunkiness:
|
|
return "Content chunkiness unknown.";
|
|
}
|
|
|
|
return "Unknown error";
|
|
}
|
|
|
|
bool vw_HTTPRequest_destroy(vw_HTTPRequest *http) {
|
|
if (!http) {
|
|
return false;
|
|
}
|
|
|
|
const bool flushed = vs_Stream_flush(http->stream);
|
|
const bool destroyed = vs_HMap_destroy_free(&http->headers);
|
|
const bool decapitated =
|
|
http->res.headered ? true : vs_HMap_destroy_free(&http->res.headers_buf);
|
|
const bool body_destroyed = vw_HTTPBody_destroy(&http->body);
|
|
|
|
return flushed && destroyed && decapitated && body_destroyed;
|
|
}
|
|
|
|
size_t vw_HTTPRequest_res_begin(vw_HTTPRequest *http, const uint16_t code) {
|
|
if (!http) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
size_t idx = 0;
|
|
size_t total_written = 0;
|
|
size_t now_written = 0;
|
|
|
|
http->res.code = code;
|
|
|
|
VS_IO_WITH_TEST(vs_Stream_writef(http->stream,
|
|
"%s %u %s" VW_HTTP_CRLF,
|
|
http->version,
|
|
code,
|
|
vw_HTTP_code_to_message(code)),
|
|
now_written,
|
|
VS_STREAM_ERROR,
|
|
total_written);
|
|
|
|
http->res.began = true;
|
|
|
|
/* Write the date */
|
|
|
|
vw_HTTPTime date;
|
|
vw_HTTP_time(time(NULL), date);
|
|
|
|
now_written = vs_Stream_writef(http->stream,
|
|
"connection: %s" VW_HTTP_CRLF "date: %s" VW_HTTP_CRLF
|
|
#ifndef VS_RESPONSE_NO_SERVER_TOKENS
|
|
"server: Vessel/" VS_VESSEL_HEADER_VERSION VW_HTTP_CRLF
|
|
#endif
|
|
,
|
|
http->keepalive ? "keep-alive" : "close",
|
|
date);
|
|
|
|
if (vs_Stream_ignore_err(now_written) == 0) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
total_written += now_written;
|
|
|
|
/* Write the headers in the buffer */
|
|
|
|
for (idx = 0; idx < http->res.headers_buf.size; ++idx) {
|
|
const vs_HMapEntry *entry = http->res.headers_buf.occupied_ents[idx];
|
|
|
|
now_written =
|
|
vs_Stream_writef(http->stream, "%s: %s" VW_HTTP_CRLF, (const char *)entry->key, (char *)entry->value);
|
|
|
|
if (vs_Stream_ignore_err(now_written) == 0) {
|
|
break; /* Write failed so just ignore the rest of the headers.
|
|
*/
|
|
}
|
|
|
|
total_written += now_written;
|
|
}
|
|
|
|
vs_HMap_destroy_free(&http->res.headers_buf);
|
|
|
|
return total_written;
|
|
}
|
|
|
|
size_t vw_HTTPRequest_res_header(vw_HTTPRequest *http, const char *key, const char *value) {
|
|
if (!http || !key || !value) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (http->res.headered) {
|
|
return 0;
|
|
}
|
|
|
|
if (!http->res.began) {
|
|
return vs_HMapID_is_valid(vs_HMap_insert(&http->res.headers_buf, key, vs_dupstr(value)))
|
|
? 0
|
|
: VS_STREAM_ERROR;
|
|
}
|
|
|
|
return vs_Stream_writef(http->stream, "%s: %s" VW_HTTP_CRLF, key, value);
|
|
}
|
|
|
|
size_t vw_HTTPRequest_res_headerf(vw_HTTPRequest *http, const char *key, const char *fmt, ...) {
|
|
if (!http || !key || !fmt) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (http->res.headered) {
|
|
return 0;
|
|
}
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
|
|
char value[VW_HTTP_HEADER_VALUE_MAX_LENGTH];
|
|
|
|
const ssize_t len = vsnprintf(value, VW_HTTP_HEADER_VALUE_MAX_LENGTH, fmt, args);
|
|
|
|
if (len < 0 || len >= VW_HTTP_HEADER_VALUE_MAX_LENGTH) {
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
if (http->res.began) {
|
|
return vs_Stream_writef(http->stream, "%s: %s" VW_HTTP_CRLF, key, value);
|
|
}
|
|
|
|
return vs_HMapID_is_valid(vs_HMap_insert(&http->res.headers_buf, key, vs_dupstr(value)))
|
|
? 0
|
|
: VS_STREAM_ERROR;
|
|
}
|
|
|
|
size_t vw_HTTPRequest_res_headers_end(vw_HTTPRequest *http) {
|
|
if (!http) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (!http->res.began || http->res.headered) {
|
|
return 0;
|
|
}
|
|
|
|
http->res.headered = true;
|
|
return vs_Stream_write(http->stream, VW_HTTP_CRLF, VW_HTTP_CRLF_LENGTH);
|
|
}
|
|
|
|
size_t vw_HTTPRequest_write(vw_HTTPRequest *http, const void *buf, size_t count) {
|
|
if (!http) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (!buf || count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (vs_Stream_ignore_err(vw_HTTPRequest_res_headerf(http, "content-length", "%zu", count)) ==
|
|
0) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
vw_HTTPRequest_res_headers_end(http);
|
|
|
|
return vw_HTTPBody_write(&http->body, buf, count);
|
|
}
|
|
|
|
size_t vw_HTTPRequest_write_chunk(vw_HTTPRequest *http, const void *buf, size_t count) {
|
|
if (!http) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
if (!buf || count == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (vw_HTTPRequest_res_header(http, "transfer-encoding", "chunked") == VS_STREAM_ERROR) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
vw_HTTPRequest_res_headers_end(http);
|
|
|
|
return vw_HTTPBody_write_chunk(&http->body, buf, count);
|
|
}
|
|
|
|
size_t vw_HTTPRequest_writef(vw_HTTPRequest *http, const char *fmt, ...) {
|
|
if (!http) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
if (!fmt || !*fmt) {
|
|
return 0;
|
|
}
|
|
|
|
va_list args;
|
|
char *temp_buf = NULL;
|
|
size_t size = 0;
|
|
ssize_t needed = 0;
|
|
|
|
va_start(args, fmt);
|
|
|
|
/* Determine the size needed for the buffer */
|
|
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
needed = vsnprintf(NULL, 0, fmt, args_copy) + 1; /* +1 for null terminator */
|
|
va_end(args_copy);
|
|
|
|
if (needed <= 0) {
|
|
va_end(args);
|
|
return 0;
|
|
}
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Alocate a dynamic temporary buffer */
|
|
|
|
temp_buf = VS_MALLOC(size);
|
|
|
|
if (!temp_buf) {
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
/* Format the string into the allocated buffer */
|
|
|
|
needed = vsnprintf(temp_buf, size, fmt, args);
|
|
|
|
if (needed < 0) {
|
|
VS_FREE(temp_buf);
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Now, just use the write abstraction + write length */
|
|
|
|
if (vs_Stream_ignore_err(vw_HTTPRequest_res_headerf(http, "content-length", "%zu", size)) ==
|
|
0) {
|
|
VS_FREE(temp_buf);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
vw_HTTPRequest_res_headers_end(http);
|
|
|
|
size = vw_HTTPBody_write(&http->body, temp_buf, size);
|
|
VS_FREE(temp_buf);
|
|
return size;
|
|
}
|
|
|
|
size_t vw_HTTPRequest_writef_chunk(vw_HTTPRequest *http, const char *fmt, ...) {
|
|
if (!http) {
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
if (!fmt || !*fmt) {
|
|
return 0;
|
|
}
|
|
|
|
va_list args;
|
|
char *temp_buf = NULL;
|
|
size_t size = 0;
|
|
ssize_t needed = 0;
|
|
|
|
va_start(args, fmt);
|
|
|
|
/* Determine the size needed for the buffer */
|
|
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
needed = vsnprintf(NULL, 0, fmt, args_copy) + 1; /* +1 for null terminator */
|
|
va_end(args_copy);
|
|
|
|
if (needed <= 0) {
|
|
va_end(args);
|
|
return 0;
|
|
}
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Alocate a dynamic temporary buffer */
|
|
|
|
temp_buf = VS_MALLOC(size);
|
|
|
|
if (!temp_buf) {
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
/* Format the string into the allocated buffer */
|
|
|
|
needed = vsnprintf(temp_buf, size, fmt, args);
|
|
|
|
if (needed < 0) {
|
|
VS_FREE(temp_buf);
|
|
va_end(args);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
size = (size_t)needed;
|
|
|
|
/* Now, just use the write abstraction + write headers */
|
|
|
|
if (vw_HTTPRequest_res_header(http, "transfer-encoding", "chunked") == VS_STREAM_ERROR) {
|
|
VS_FREE(temp_buf);
|
|
return VS_STREAM_ERROR;
|
|
}
|
|
vw_HTTPRequest_res_headers_end(http);
|
|
|
|
if (vs_Stream_ignore_err(vw_HTTPBody_writef(&http->body, "%zX" VW_HTTP_CRLF, size)) <
|
|
VW_HTTP_CRLF_LENGTH + 1) {
|
|
size = VS_STREAM_ERROR;
|
|
} else {
|
|
size = vw_HTTPBody_write(&http->body, temp_buf, size);
|
|
|
|
if (vs_Stream_ignore_err(size) != 0 &&
|
|
vw_HTTPBody_write(&http->body, VW_HTTP_CRLF, VW_HTTP_CRLF_LENGTH) !=
|
|
VW_HTTP_CRLF_LENGTH) {
|
|
size = VS_STREAM_ERROR;
|
|
}
|
|
}
|
|
|
|
VS_FREE(temp_buf);
|
|
return size;
|
|
}
|