vessel/web/request.c
Arija A. 4f0bf80e5f
refact: Remove mem.h
Signed-off-by: Arija A. <ari@ari.lt>
2025-06-21 23:43:31 +03:00

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;
}