881 lines
26 KiB
C
881 lines
26 KiB
C
#include "include/conf.h"
|
|
|
|
#include <vessel/def.h>
|
|
#include <vessel/clrs.h>
|
|
#include <vessel/switch.h>
|
|
|
|
#include "include/http.h"
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
/* TODO: Make a function to read an HTTP chunk. */
|
|
|
|
const char *vw_HTTP_code_to_message(const uint16_t code) {
|
|
switch (code) {
|
|
/* 1xx */
|
|
|
|
case 100:
|
|
return "Continue";
|
|
case 101:
|
|
return "Switching Protocols";
|
|
case 102:
|
|
return "Processing";
|
|
case 103:
|
|
return "Early Hints";
|
|
|
|
/* 2xx */
|
|
case 200:
|
|
return "OK";
|
|
case 201:
|
|
return "Created";
|
|
case 202:
|
|
return "Accepted";
|
|
case 203:
|
|
return "Non-Authoritative Information";
|
|
case 204:
|
|
return "No Content";
|
|
case 205:
|
|
return "Reset Content";
|
|
case 206:
|
|
return "Partial Content";
|
|
case 207:
|
|
return "Multi-Status";
|
|
case 208:
|
|
return "Already Reported";
|
|
case 226:
|
|
return "IM Used";
|
|
|
|
/* 3xx */
|
|
case 300:
|
|
return "Multiple Choices";
|
|
case 301:
|
|
return "Moved Permanently";
|
|
case 302:
|
|
return "Found";
|
|
case 303:
|
|
return "See Other";
|
|
case 304:
|
|
return "Not Modified";
|
|
case 305:
|
|
return "Use Proxy";
|
|
case 306:
|
|
return "Switch Proxy";
|
|
case 307:
|
|
return "Temporary Redirect";
|
|
case 308:
|
|
return "Permanent Redirect";
|
|
|
|
/* 4xx */
|
|
|
|
case 400:
|
|
return "Bad Request";
|
|
case 401:
|
|
return "Unauthorized";
|
|
case 402:
|
|
return "Payment Required";
|
|
case 403:
|
|
return "Forbidden";
|
|
case 404:
|
|
return "Not Found";
|
|
case 405:
|
|
return "Method Not Allowed";
|
|
case 406:
|
|
return "Not Acceptable";
|
|
case 407:
|
|
return "Proxy Authentication Required";
|
|
case 408:
|
|
return "Request Timeout";
|
|
case 409:
|
|
return "Conflict";
|
|
case 410:
|
|
return "Gone";
|
|
case 411:
|
|
return "Length Required";
|
|
case 412:
|
|
return "Precondition Failed";
|
|
case 413:
|
|
return "Payload Too Large";
|
|
case 414:
|
|
return "URI Too Long";
|
|
case 415:
|
|
return "Unsupported Media Type";
|
|
case 416:
|
|
return "Range Not Satisfiable";
|
|
case 417:
|
|
return "Expectation Failed";
|
|
case 418:
|
|
return "I'm a teapot";
|
|
case 419:
|
|
return "Page Expired";
|
|
case 420:
|
|
return "Enhance Your Calm";
|
|
case 421:
|
|
return "Misdirected Request";
|
|
case 422:
|
|
return "Unprocessable Content";
|
|
case 423:
|
|
return "Locked";
|
|
case 424:
|
|
return "Failed Dependency";
|
|
case 425:
|
|
return "Too Early";
|
|
case 426:
|
|
return "Upgrade Required";
|
|
case 428:
|
|
return "Precondition Required";
|
|
case 429:
|
|
return "Too Many Requests";
|
|
case 431:
|
|
return "Request Header Fields Too Large";
|
|
case 444:
|
|
return "No Response";
|
|
case 451:
|
|
return "Unavailable For Legal Reasons";
|
|
|
|
/* 5xx */
|
|
|
|
case 500:
|
|
return "Internal Server Error";
|
|
case 501:
|
|
return "Not Implemented";
|
|
case 502:
|
|
return "Bad Gateway";
|
|
case 503:
|
|
return "Service Unavailable";
|
|
case 504:
|
|
return "Gateway Timeout";
|
|
case 505:
|
|
return "HTTP Version Not Supported";
|
|
case 506:
|
|
return "Variant Also Negotiates";
|
|
case 507:
|
|
return "Insufficient Storage";
|
|
case 508:
|
|
return "Loop Detected";
|
|
case 510:
|
|
return "Not Extended";
|
|
case 511:
|
|
return "Network Authentication Required";
|
|
|
|
/* 6xx */
|
|
|
|
/* HTTP/666: Satan's Favourite (custom Vessel server HTTP code)
|
|
*
|
|
* This code is used mainly as a way to indicate an expected error,
|
|
* blockage, or abortion. To you this may mean one thing - content
|
|
* may be incomplete and premature. To the user this may indicate
|
|
* that abuse was detected or they were blocked in some other way,
|
|
* or that some internal state got messed up and detected therefore
|
|
* an abortion of the request on the TCP level happened.
|
|
*
|
|
* "Satan's Favourite" refers to bad luck on the user's side, or if
|
|
* they were being evil - they are the Satan per se. The status
|
|
* number and message are mainly novelty, honestly.
|
|
*
|
|
* TL;DR Something (unsure what) got messed up, we intentionally
|
|
* messed it up you, or detected you trying to do so. Therefore,
|
|
* goodbye.
|
|
*/
|
|
case 666:
|
|
return "Satan's Favourite";
|
|
|
|
/* Else */
|
|
|
|
default:
|
|
return "Unknown Status";
|
|
}
|
|
}
|
|
|
|
const char *vw_HTTP_code_to_description(uint16_t code) {
|
|
switch (code) {
|
|
/* 1xx Informational */
|
|
case 100:
|
|
return "The server has received your request and you can continue sending the rest of "
|
|
"it";
|
|
case 101:
|
|
return "The server is switching to a different communication protocol as requested";
|
|
case 102:
|
|
return "The server is still processing your request; please wait";
|
|
case 103:
|
|
return "Here are some preliminary hints while the server prepares the full response";
|
|
|
|
/* 2xx Success */
|
|
case 200:
|
|
return "Your request was successful and the server returned the requested information";
|
|
case 201:
|
|
return "Your request was successful and a new resource was created";
|
|
case 202:
|
|
return "Your request has been accepted and is being processed";
|
|
case 203:
|
|
return "The information returned comes from a third party, not the original server";
|
|
case 204:
|
|
return "Your request was successful but there is no content to show";
|
|
case 205:
|
|
return "Your request was successful; please reset the view or form";
|
|
case 206:
|
|
return "Only part of the requested content is being sent back";
|
|
case 207:
|
|
return "The server is returning multiple pieces of information in one response";
|
|
case 208:
|
|
return "Some information has already been reported earlier, so it's not repeated";
|
|
case 226:
|
|
return "The server has fulfilled your request with some additional processing";
|
|
|
|
/* 3xx Redirection */
|
|
case 300:
|
|
return "There are multiple options for the resource you requested; please choose one";
|
|
case 301:
|
|
return "The page you requested has moved permanently to a new address";
|
|
case 302:
|
|
return "The page you requested has moved temporarily to a different address";
|
|
case 303:
|
|
return "Please look at a different page to find the resource you want";
|
|
case 304:
|
|
return "The page has not changed since your last visit; you can use your cached copy";
|
|
case 305:
|
|
return "You need to use a proxy server to access the requested page";
|
|
case 306:
|
|
return "Please use a different proxy server to access the requested resource";
|
|
case 307:
|
|
return "Please try your request again at a different address temporarily";
|
|
case 308:
|
|
return "Please use the new permanent address for future requests";
|
|
|
|
/* 4xx Client Errors */
|
|
case 400:
|
|
return "Your request was invalid or cannot be processed by the server";
|
|
case 401:
|
|
return "You need to log in to access this resource";
|
|
case 402:
|
|
return "Payment is required to access this resource";
|
|
case 403:
|
|
return "You don't have permission to access this page";
|
|
case 404:
|
|
return "The requested page or resource could not be found on the server";
|
|
case 405:
|
|
return "The method you used is not allowed for this page";
|
|
case 406:
|
|
return "The server cannot provide the content in a format you requested";
|
|
case 407:
|
|
return "You need to authenticate with the proxy server before proceeding";
|
|
case 408:
|
|
return "Your request took too long to complete; please try again";
|
|
case 409:
|
|
return "There is a conflict with the current state of the resource";
|
|
case 410:
|
|
return "The requested resource has been permanently removed";
|
|
case 411:
|
|
return "The server requires a content length to process your request";
|
|
case 412:
|
|
return "Some conditions in your request were not met";
|
|
case 413:
|
|
return "The data you sent is too large for the server to process";
|
|
case 414:
|
|
return "The URL you entered is too long for the server to handle";
|
|
case 415:
|
|
return "The server does not support the format of the data you sent";
|
|
case 416:
|
|
return "The range you requested is not available";
|
|
case 417:
|
|
return "The server cannot meet the requirements you specified";
|
|
case 418:
|
|
return "I'm a teapot - the server is overloaded";
|
|
case 419:
|
|
return "The page has expired; please refresh and try again";
|
|
case 420:
|
|
return "You are sending requests too quickly; please slow down";
|
|
case 421:
|
|
return "Your request was sent to the wrong server; please try again";
|
|
case 422:
|
|
return "Your request was well-formed but contains semantic errors";
|
|
case 423:
|
|
return "The resource you are trying to access is locked";
|
|
case 424:
|
|
return "Your request failed because a previous request it depended on failed";
|
|
case 425:
|
|
return "The server is not ready to process your request yet";
|
|
case 426:
|
|
return "You need to upgrade your protocol to continue";
|
|
case 428:
|
|
return "The server requires certain conditions to be met before processing your "
|
|
"request";
|
|
case 429:
|
|
return "You have sent too many requests in a short time; please wait and try again";
|
|
case 431:
|
|
return "Your request headers are too large for the server to process";
|
|
case 444:
|
|
return "The server closed the connection without sending a response";
|
|
case 451:
|
|
return "Access to this resource is denied for legal reasons";
|
|
|
|
/* 5xx Server Errors */
|
|
case 500:
|
|
return "The server encountered an error and could not complete your request";
|
|
case 501:
|
|
return "The server does not support the functionality required to fulfill your "
|
|
"request";
|
|
case 502:
|
|
return "The server received an invalid response from an upstream server";
|
|
case 503:
|
|
return "The server is currently unavailable, usually due to maintenance or overload";
|
|
case 504:
|
|
return "The server did not receive a timely response from another server";
|
|
case 505:
|
|
return "The server does not support the HTTP protocol version used in your request";
|
|
case 506:
|
|
return "The server has a configuration error preventing your request from being "
|
|
"fulfilled";
|
|
case 507:
|
|
return "The server is unable to store the necessary data to complete your request";
|
|
case 508:
|
|
return "The server detected an infinite loop while processing your request";
|
|
case 510:
|
|
return "Further extensions to your request are required for the server to fulfill it";
|
|
case 511:
|
|
return "You need to authenticate to gain network access";
|
|
|
|
/* 6xx Custom */
|
|
case 666:
|
|
return "Your request was blocked or aborted due to suspicious activity";
|
|
|
|
/* Default */
|
|
default:
|
|
return "An unknown error occurred; the server returned an unrecognized status code";
|
|
}
|
|
}
|
|
|
|
const char *vw_HTTP_code_to_colour(const uint16_t code) {
|
|
if (code >= 600) {
|
|
return VS_CLR_BOLD VS_CLR_UNDERLINE VS_CLR_RED;
|
|
}
|
|
if (code >= 500) {
|
|
return VS_CLR_BOLD VS_CLR_MAGENTA;
|
|
}
|
|
if (code >= 400) {
|
|
return VS_CLR_BOLD VS_CLR_RED;
|
|
}
|
|
if (code >= 300) {
|
|
return VS_CLR_BOLD VS_CLR_YELLOW;
|
|
}
|
|
if (code >= 200) {
|
|
return VS_CLR_BOLD VS_CLR_GREEN;
|
|
}
|
|
if (code >= 100) {
|
|
return VS_CLR_BOLD VS_CLR_CYAN;
|
|
}
|
|
|
|
return VS_CLR_BOLD;
|
|
}
|
|
|
|
ssize_t vw_HTTP_url_decode(char *str) {
|
|
if (!str) {
|
|
return -1;
|
|
}
|
|
|
|
char a = 0;
|
|
char b = 0;
|
|
char c = 0;
|
|
|
|
char *read_ptr = str;
|
|
char *write_ptr = str;
|
|
|
|
/* NOTE: Skips NULL bytes. */
|
|
|
|
while (*read_ptr) {
|
|
if ((*read_ptr == '%') && (read_ptr[1] && read_ptr[2]) &&
|
|
isxdigit((unsigned char)read_ptr[1]) && isxdigit((unsigned char)read_ptr[2])) {
|
|
a = read_ptr[1];
|
|
b = read_ptr[2];
|
|
|
|
a = (char)((a >= 'a') ? (a - 'a' + 10) : (a >= 'A' ? a - 'A' + 10 : a - '0'));
|
|
b = (char)((b >= 'a') ? (b - 'a' + 10) : (b >= 'A' ? b - 'A' + 10 : b - '0'));
|
|
|
|
c = (char)((16 * a) + b);
|
|
|
|
read_ptr += 3;
|
|
|
|
if (c) {
|
|
*write_ptr++ = c;
|
|
}
|
|
} else if (*read_ptr == '+') {
|
|
*write_ptr++ = ' ';
|
|
read_ptr++;
|
|
} else {
|
|
c = *read_ptr++;
|
|
|
|
if (c == '\0') {
|
|
continue;
|
|
}
|
|
|
|
*write_ptr++ = c;
|
|
}
|
|
}
|
|
|
|
*write_ptr = '\0';
|
|
|
|
return (ssize_t)(write_ptr - str);
|
|
}
|
|
|
|
char *vw_HTTP_html_encode(const char *str) {
|
|
if (!str) {
|
|
return NULL;
|
|
}
|
|
|
|
size_t len = 0;
|
|
const char *p = str;
|
|
|
|
while (*p) {
|
|
switch (*p) {
|
|
case '&':
|
|
len += 5;
|
|
break; /* & */
|
|
case '<':
|
|
len += 4;
|
|
break; /* < */
|
|
case '>':
|
|
len += 4;
|
|
break; /* > */
|
|
case '"':
|
|
len += 6;
|
|
break; /* " */
|
|
case '\'':
|
|
len += 6;
|
|
break; /* ' */
|
|
default:
|
|
len += 1;
|
|
break;
|
|
}
|
|
++p;
|
|
}
|
|
|
|
char *out = VS_MALLOC(len + 1);
|
|
if (!out) {
|
|
return NULL;
|
|
}
|
|
|
|
char *q = out;
|
|
while (*str) {
|
|
switch (*str) {
|
|
case '&':
|
|
memcpy(q, "&", 5);
|
|
q += 5;
|
|
break;
|
|
case '<':
|
|
memcpy(q, "<", 4);
|
|
q += 4;
|
|
break;
|
|
case '>':
|
|
memcpy(q, ">", 4);
|
|
q += 4;
|
|
break;
|
|
case '"':
|
|
memcpy(q, """, 6);
|
|
q += 6;
|
|
break;
|
|
case '\'':
|
|
memcpy(q, "'", 5);
|
|
q += 5;
|
|
break;
|
|
default:
|
|
*q++ = *str;
|
|
break;
|
|
}
|
|
++str;
|
|
}
|
|
*q = '\0';
|
|
|
|
return out;
|
|
}
|
|
|
|
ssize_t vw_HTTP_html_decode(char *str) {
|
|
if (!str) {
|
|
return -1;
|
|
}
|
|
|
|
char *src = str, *dst = str;
|
|
while (*src) {
|
|
if (*src == '&') {
|
|
if (!strncmp(src, "&", 5)) {
|
|
*dst++ = '&';
|
|
src += 5;
|
|
} else if (!strncmp(src, "<", 4)) {
|
|
*dst++ = '<';
|
|
src += 4;
|
|
} else if (!strncmp(src, ">", 4)) {
|
|
*dst++ = '>';
|
|
src += 4;
|
|
} else if (!strncmp(src, """, 6)) {
|
|
*dst++ = '"';
|
|
src += 6;
|
|
} else if (!strncmp(src, "'", 5)) {
|
|
*dst++ = '\'';
|
|
src += 5;
|
|
} else {
|
|
*dst++ = *src++;
|
|
}
|
|
} else {
|
|
*dst++ = *src++;
|
|
}
|
|
}
|
|
*dst = '\0';
|
|
|
|
return dst - str;
|
|
}
|
|
|
|
bool vw_HTTP_parse_query(const char *param_start, vs_HMap *out) {
|
|
if (!param_start || !out) {
|
|
return false;
|
|
}
|
|
|
|
while (*param_start) {
|
|
const char *equal_sign = strchr(param_start, '=');
|
|
const char *next_param = strchr(param_start, '&');
|
|
|
|
if (!next_param) {
|
|
next_param = param_start + strlen(param_start);
|
|
}
|
|
|
|
const size_t key_len = equal_sign && equal_sign < next_param
|
|
? (size_t)(equal_sign - param_start)
|
|
: (size_t)(next_param - param_start);
|
|
|
|
if (key_len == 0) {
|
|
/* Skip empty keys */
|
|
param_start = next_param + (*next_param ? 1 : 0);
|
|
continue;
|
|
}
|
|
|
|
char *key = VS_MALLOC(key_len + 1);
|
|
|
|
if (!key) {
|
|
return false;
|
|
}
|
|
|
|
strncpy(key, param_start, key_len);
|
|
key[key_len] = '\0';
|
|
|
|
if (equal_sign && equal_sign < next_param) {
|
|
const size_t value_len = (size_t)(next_param - (equal_sign + 1));
|
|
|
|
if (value_len == 0) {
|
|
goto insert_empty;
|
|
}
|
|
|
|
char *value = VS_MALLOC(value_len + 1);
|
|
|
|
if (!value) {
|
|
VS_FREE(key);
|
|
return false;
|
|
}
|
|
|
|
strncpy(value, equal_sign + 1, value_len);
|
|
value[value_len] = '\0';
|
|
|
|
if (vw_HTTP_url_decode(value) == -1) {
|
|
VS_FREE(key);
|
|
VS_FREE(value);
|
|
return false;
|
|
}
|
|
|
|
if (!vs_HMapID_is_valid(vs_HMap_insert(out, key, value))) {
|
|
VS_FREE(key);
|
|
VS_FREE(value);
|
|
return false;
|
|
}
|
|
} else {
|
|
insert_empty:
|
|
vs_HMap_insert(out, key, NULL);
|
|
}
|
|
|
|
VS_FREE(key);
|
|
param_start = next_param + (*next_param ? 1 : 0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* TODO: Make this validator more standard */
|
|
bool vw_HTTP_validate_request_headers(const void *request, size_t size) {
|
|
/* The most minimal request "could" look like `GET / HTTP/2\r\n\r\n` which
|
|
* is 16 bytes. */
|
|
|
|
if (!request || size < VW_HTTP_SMALLEST_REQUEST) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t *ptr = request;
|
|
const uint8_t *end = (const uint8_t *)request + size;
|
|
|
|
while (ptr < end) {
|
|
if (*ptr < 0x20 && *ptr != '\r' && *ptr != '\n') {
|
|
return false;
|
|
}
|
|
|
|
/* Check for CRLF sequence */
|
|
|
|
if (*ptr == '\r') {
|
|
/* \r should always be led by \n. If it is at the end - it cannot be
|
|
* terminated by an LF. */
|
|
|
|
/* The space is for multi-line headers. */
|
|
if (ptr + 1 >= end || (*(ptr + 1) != '\n' && *(ptr + 1) != ' ')) {
|
|
return false; /* Missing LF after CR */
|
|
}
|
|
|
|
++ptr; /* Move past LF */
|
|
} else if (*ptr == '\n') {
|
|
/* The space is for multi-line headers. */
|
|
|
|
if (ptr + 1 >= end || *(ptr + 1) != ' ') {
|
|
/* LF without preceding CR. This case should not be hit if we hit \r
|
|
* already. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
++ptr;
|
|
}
|
|
|
|
/* LGTM! The last 4 bytes should be verified by the user. */
|
|
|
|
return true;
|
|
}
|
|
|
|
size_t vw_HTTP_time(const time_t ts, vw_HTTPTime out) {
|
|
if (!out) {
|
|
return 0;
|
|
}
|
|
|
|
return strftime(out, VW_HTTP_TIME_SIZE, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&ts));
|
|
}
|
|
|
|
static char *vw_HTTP_parse_multipart_header_parse(
|
|
char *ptr, char *out, size_t out_size, const char terminator, const char fallback, bool *fell) {
|
|
bool in_quotes = false;
|
|
size_t idx = 0;
|
|
|
|
if (fell) {
|
|
*fell = false;
|
|
}
|
|
|
|
while (*ptr && idx < out_size) {
|
|
switch (*ptr) {
|
|
case '"':
|
|
in_quotes = !in_quotes;
|
|
++ptr;
|
|
break;
|
|
case '\\':
|
|
if (in_quotes) {
|
|
++ptr; /* Skip backslash */
|
|
if (*ptr) {
|
|
out[idx++] = *(ptr++); /* Save the escaped character */
|
|
} else {
|
|
return NULL; /* Backslash at the end. */
|
|
}
|
|
} else {
|
|
return NULL; /* Invalid escape outside quotes */
|
|
}
|
|
break;
|
|
case ' ':
|
|
if (in_quotes) {
|
|
out[idx++] = ' ';
|
|
}
|
|
++ptr;
|
|
break;
|
|
default:
|
|
if (*ptr == terminator) {
|
|
if (in_quotes) {
|
|
out[idx++] = *(ptr++);
|
|
} else {
|
|
*ptr = '\0';
|
|
ptr++;
|
|
goto end;
|
|
}
|
|
} else if (!in_quotes && fallback && *ptr == fallback) {
|
|
if (fell) {
|
|
*fell = true;
|
|
}
|
|
*ptr = '\0';
|
|
ptr++;
|
|
goto end;
|
|
} else {
|
|
out[idx++] = *(ptr++);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
end:
|
|
if (in_quotes) {
|
|
return NULL;
|
|
}
|
|
|
|
out[idx] = '\0';
|
|
|
|
return ptr;
|
|
}
|
|
|
|
bool vw_HTTP_parse_multipart_header(const char *value, vs_HMap *out) {
|
|
if (!value || !out) {
|
|
return false;
|
|
}
|
|
|
|
char *ptr = vs_dupstr(value);
|
|
char *og_ptr = ptr;
|
|
|
|
if (!ptr) {
|
|
return false;
|
|
}
|
|
|
|
char key[128];
|
|
char val[VW_HTTP_HEADER_VALUE_MAX_LENGTH - sizeof(key)];
|
|
|
|
while (*ptr) {
|
|
bool fell = false;
|
|
|
|
/*
|
|
* TODO: Make the parser less tolerant to fallthroughs?
|
|
*
|
|
* For instance
|
|
*
|
|
* Content-Disposition: form-data; name="file"; filename="test"
|
|
*
|
|
* should be valid, although,
|
|
*
|
|
* Content-Disposition: name="file"; form-data; filename="test"
|
|
* (fallthrough mid header)
|
|
*
|
|
* and
|
|
*
|
|
* Content-Disposition: form-data; meow; name="file";
|
|
* filename="test" (double fallthrough)
|
|
*
|
|
* should not.
|
|
*/
|
|
|
|
if (!(ptr = vw_HTTP_parse_multipart_header_parse(ptr, key, sizeof(key), '=', ';', &fell))) {
|
|
goto error;
|
|
}
|
|
|
|
vs_lowstr(key);
|
|
|
|
if (fell) {
|
|
if (!vs_HMapID_is_valid(vs_HMap_insert(out, key, NULL))) {
|
|
goto error;
|
|
}
|
|
} else {
|
|
if (!(ptr = vw_HTTP_parse_multipart_header_parse(
|
|
ptr, val, sizeof(val), ';', '\0', NULL))) {
|
|
goto error;
|
|
}
|
|
|
|
if (!vs_HMapID_is_valid(vs_HMap_insert(out, key, vs_dupstr(val)))) {
|
|
goto error;
|
|
}
|
|
}
|
|
}
|
|
|
|
VS_FREE(og_ptr);
|
|
return true;
|
|
|
|
error:
|
|
VS_FREE(og_ptr);
|
|
return false;
|
|
}
|
|
|
|
/* TODO: Implement better header parsing & proper multi-line headers */
|
|
bool vw_HTTP_parse_headers(const void *data, size_t buf_size, vs_HMap *out, size_t *idx) {
|
|
size_t new_idx = 0;
|
|
|
|
if (!data || buf_size == 0 || !out) {
|
|
return false;
|
|
}
|
|
|
|
if (!idx) {
|
|
idx = &new_idx;
|
|
}
|
|
|
|
char key[VW_HTTP_HEADER_KEY_MAX_LENGTH];
|
|
char value[VW_HTTP_HEADER_VALUE_MAX_LENGTH];
|
|
const char *buf = (const char *)data;
|
|
|
|
while (*idx < buf_size && buf[*idx]) {
|
|
size_t jdx = 0;
|
|
|
|
/* Read header key */
|
|
|
|
while (buf[*idx] != ':' && buf[*idx] != '\r' && jdx < VW_HTTP_HEADER_KEY_MAX_LENGTH - 1) {
|
|
key[jdx++] = buf[(*idx)++];
|
|
}
|
|
key[jdx] = '\0';
|
|
|
|
/* Check for colon, and then skip it */
|
|
|
|
if (buf[*idx] != ':') {
|
|
return false;
|
|
}
|
|
|
|
++(*idx);
|
|
|
|
/* Skip optional spaces after colon */
|
|
|
|
while (*idx < buf_size && buf[*idx] == ' ') {
|
|
++(*idx);
|
|
}
|
|
|
|
/* Read header value */
|
|
|
|
jdx = 0;
|
|
while (*idx < buf_size && buf[*idx] != '\r' && jdx < VW_HTTP_HEADER_VALUE_MAX_LENGTH - 1) {
|
|
value[jdx++] = buf[(*idx)++];
|
|
}
|
|
value[jdx] = '\0';
|
|
|
|
/* Check for CRLF after header value and skip it. Ignores last one as it
|
|
* does not have it (due to readb()). */
|
|
|
|
if (*idx < buf_size) {
|
|
if (strncmp(buf + *idx, VW_HTTP_CRLF, VW_HTTP_CRLF_LENGTH) != 0) {
|
|
return false;
|
|
}
|
|
|
|
(*idx) += VW_HTTP_CRLF_LENGTH;
|
|
}
|
|
|
|
/* Convert key to lowercase and insert into headers map if it doesn't
|
|
* exist */
|
|
|
|
vs_lowstr(key);
|
|
|
|
if (!vs_HMap_find(out, key)) {
|
|
/* Common headers which have case-insensitive values. */
|
|
switch (vs_switch_hash(key)) {
|
|
case VW_HTTP_ACCEPT_HASH:
|
|
case VW_HTTP_USER_AGENT_HASH:
|
|
case VW_HTTP_CACHE_CONTROL_HASH:
|
|
case VW_HTTP_ACCEPT_ENCODING_HASH:
|
|
case VW_HTTP_ACCEPT_LANGUAGE_HASH:
|
|
case VW_HTTP_ORIGIN_HASH:
|
|
case VW_HTTP_HOST_HASH:
|
|
case VW_HTTP_ACCESS_CONTROL_REQUEST_METHOD_HASH:
|
|
case VW_HTTP_ACCESS_CONTROL_REQUEST_HEADERS_HASH:
|
|
case VW_HTTP_RANGE_HASH:
|
|
case VW_HTTP_X_REQUESTED_WITH_HASH:
|
|
case VW_HTTP_CONNECTION_HASH:
|
|
case VW_HTTP_ACCEPT_RANGES_HASH:
|
|
case VW_HTTP_CONTENT_ENCODING_HASH:
|
|
case VW_HTTP_EXPECT_HASH:
|
|
case VW_HTTP_TE_HASH:
|
|
vs_lowstr(value);
|
|
}
|
|
|
|
if (!vs_HMapID_is_valid(vs_HMap_insert(out, key, vs_dupstr(value)))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|