Fix TODOs unrelated to HTTP client
- Add documentation comments for print_bytes() and HTTP_UNPACK macro in src/basic.h - Improve error handling in src/socket.c: add checks for ioctlsocket, socket creation, and connection failures; implement socket_manager_wakeup with proper signaling; consume wakeup signals; add memory cleanup for registered names - Add header validation in src/server.c to prevent HTTP response splitting attacks - Enhance HTTP parsing in src/parse.c: support percent-encoded characters in userinfo, expand HTTP methods without request bodies, add response body detection logic, improve cookie parsing comments - Document unused 'ad' parameter in src/secure_context.c SNI callback
This commit is contained in:
+5
-2
@@ -19,7 +19,8 @@ bool http_streqcase(HTTP_String s1, HTTP_String s2);
|
||||
// the new one references the contents of the original one.
|
||||
HTTP_String http_trim(HTTP_String s);
|
||||
|
||||
// TODO: comment
|
||||
// Print the contents of a byte string with the given prefix.
|
||||
// This is primarily used for debugging purposes.
|
||||
void print_bytes(HTTP_String prefix, HTTP_String src);
|
||||
|
||||
// Macro to simplify converting string literals to
|
||||
@@ -46,5 +47,7 @@ void print_bytes(HTTP_String prefix, HTTP_String src);
|
||||
// Returns the number of items of a static array.
|
||||
#define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0]))
|
||||
|
||||
// TODO: comment
|
||||
// Macro to unpack an HTTP_String into its length and pointer components.
|
||||
// Useful for passing HTTP_String to printf-style functions with "%.*s" format.
|
||||
// Example: printf("%.*s", HTTP_UNPACK(str));
|
||||
#define HTTP_UNPACK(X) (X).len, (X).ptr
|
||||
|
||||
+24
-5
@@ -432,9 +432,10 @@ static int is_scheme_body(char c)
|
||||
}
|
||||
|
||||
// userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
|
||||
// Note: percent-encoded characters (%XX) are not currently validated
|
||||
static int is_userinfo(char c)
|
||||
{
|
||||
return is_unreserved(c) || is_sub_delim(c) || c == ':'; // TODO: PCT encoded
|
||||
return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '%';
|
||||
}
|
||||
|
||||
// authority = [ userinfo "@" ] host [ ":" port ]
|
||||
@@ -1033,8 +1034,13 @@ static int parse_request(Scanner *s, HTTP_Request *req)
|
||||
return num_headers;
|
||||
req->num_headers = num_headers;
|
||||
|
||||
// Request methods that typically don't have a body
|
||||
bool body_expected = true;
|
||||
if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods?
|
||||
if (req->method == HTTP_METHOD_GET ||
|
||||
req->method == HTTP_METHOD_HEAD ||
|
||||
req->method == HTTP_METHOD_DELETE ||
|
||||
req->method == HTTP_METHOD_OPTIONS ||
|
||||
req->method == HTTP_METHOD_TRACE)
|
||||
body_expected = false;
|
||||
|
||||
return parse_body(s, req->headers, req->num_headers, &req->body, body_expected);
|
||||
@@ -1075,10 +1081,12 @@ static int parse_response(Scanner *s, HTTP_Response *res)
|
||||
(s->src[s->cur-3] - '0') * 10 +
|
||||
(s->src[s->cur-4] - '0') * 100;
|
||||
|
||||
// Parse reason phrase: HTAB / SP / VCHAR / obs-text
|
||||
// Note: obs-text (obsolete text, octets 0x80-0xFF) is not validated
|
||||
while (s->cur < s->len && (
|
||||
s->src[s->cur] == '\t' ||
|
||||
s->src[s->cur] == ' ' ||
|
||||
is_vchar(s->src[s->cur]))) // TODO: obs-text
|
||||
is_vchar(s->src[s->cur])))
|
||||
s->cur++;
|
||||
|
||||
if (s->len - s->cur < 2
|
||||
@@ -1092,7 +1100,17 @@ static int parse_response(Scanner *s, HTTP_Response *res)
|
||||
return num_headers;
|
||||
res->num_headers = num_headers;
|
||||
|
||||
bool body_expected = true; // TODO
|
||||
// Responses with certain status codes don't have a body:
|
||||
// - 1xx (Informational)
|
||||
// - 204 (No Content)
|
||||
// - 304 (Not Modified)
|
||||
// Note: HEAD responses also don't have a body, but we can't determine
|
||||
// that here without access to the request method
|
||||
bool body_expected = true;
|
||||
if ((res->status >= 100 && res->status < 200) ||
|
||||
res->status == 204 ||
|
||||
res->status == 304)
|
||||
body_expected = false;
|
||||
|
||||
return parse_body(s, res->headers, res->num_headers, &res->body, body_expected);
|
||||
}
|
||||
@@ -1142,7 +1160,8 @@ int http_parse_response(char *src, int len, HTTP_Response *res)
|
||||
|
||||
HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name)
|
||||
{
|
||||
// TODO: best-effort implementation
|
||||
// Simple cookie parsing - does not handle quoted values or special characters
|
||||
// See RFC 6265 for full cookie specification
|
||||
|
||||
for (int i = 0; i < req->num_headers; i++) {
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ static int servername_callback(SSL *ssl, int *ad, void *arg)
|
||||
{
|
||||
ServerSecureContext *ctx = arg;
|
||||
|
||||
(void) ad; // TODO: use this?
|
||||
// The 'ad' parameter is used to set the alert description when returning
|
||||
// SSL_TLSEXT_ERR_ALERT_FATAL. Since we only return OK or NOACK, it's unused.
|
||||
(void) ad;
|
||||
|
||||
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
if (servername == NULL)
|
||||
|
||||
+13
-1
@@ -374,7 +374,19 @@ void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str)
|
||||
if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER)
|
||||
return;
|
||||
|
||||
// TODO: Check that the header is valid
|
||||
// Validate header: must contain a colon and no control characters
|
||||
// (to prevent HTTP response splitting attacks)
|
||||
bool has_colon = false;
|
||||
for (int i = 0; i < str.len; i++) {
|
||||
char c = str.ptr[i];
|
||||
if (c == ':')
|
||||
has_colon = true;
|
||||
// Reject control characters (especially \r and \n)
|
||||
if (c < 0x20 && c != '\t')
|
||||
return;
|
||||
}
|
||||
if (!has_colon)
|
||||
return;
|
||||
|
||||
byte_queue_write(&conn->output, str.ptr, str.len);
|
||||
byte_queue_write(&conn->output, "\r\n", 2);
|
||||
|
||||
+53
-16
@@ -32,7 +32,10 @@ static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b)
|
||||
// Optional: Set socket to non-blocking mode
|
||||
// This prevents send() from blocking if the receive buffer is full
|
||||
u_long mode = 1;
|
||||
ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail?
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*a = sock;
|
||||
*b = sock;
|
||||
@@ -108,12 +111,12 @@ static NATIVE_SOCKET create_listen_socket(HTTP_String addr,
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_addr = addr_buf;
|
||||
bind_buf.sin_port = htons(port);
|
||||
if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows?
|
||||
if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) {
|
||||
CLOSE_NATIVE_SOCKET(sock);
|
||||
return NATIVE_SOCKET_INVALID;
|
||||
}
|
||||
|
||||
if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows?
|
||||
if (listen(sock, backlog) < 0) {
|
||||
CLOSE_NATIVE_SOCKET(sock);
|
||||
return NATIVE_SOCKET_INVALID;
|
||||
}
|
||||
@@ -327,7 +330,10 @@ static void socket_update(Socket *s)
|
||||
|
||||
s->next_addr++;
|
||||
if (s->next_addr == s->num_addr) {
|
||||
assert(0); // TODO
|
||||
// All addresses have been tried and failed
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
AddressAndPort addr = s->addrs[s->next_addr];
|
||||
@@ -335,11 +341,16 @@ static void socket_update(Socket *s)
|
||||
int family = (addr.is_ipv4 ? AF_INET : AF_INET6);
|
||||
NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0);
|
||||
if (sock == NATIVE_SOCKET_INVALID) {
|
||||
assert(0); // TODO
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (set_socket_blocking(sock, false) < 0) {
|
||||
assert(0); // TODO
|
||||
CLOSE_NATIVE_SOCKET(sock);
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
int ret;
|
||||
@@ -393,7 +404,10 @@ static void socket_update(Socket *s)
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(s->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) {
|
||||
assert(0); // TODO
|
||||
// Failed to get socket error status
|
||||
s->state = SOCKET_STATE_DIED;
|
||||
s->events = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (err == 0) {
|
||||
@@ -590,11 +604,21 @@ int socket_manager_wakeup(SocketManager *sm)
|
||||
if (mutex_lock(&sm->mutex) < 0)
|
||||
return -1;
|
||||
|
||||
// TODO
|
||||
// Send a byte through the signal socket to wake up any thread
|
||||
// blocked on poll() with the wait socket
|
||||
char byte = 1;
|
||||
int ret = 0;
|
||||
#ifdef _WIN32
|
||||
if (send(sm->signal_sock, &byte, 1, 0) < 0)
|
||||
ret = -1;
|
||||
#else
|
||||
if (write(sm->signal_sock, &byte, 1) < 0)
|
||||
ret = -1;
|
||||
#endif
|
||||
|
||||
if (mutex_unlock(&sm->mutex) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int socket_manager_register_events_nolock(
|
||||
@@ -752,7 +776,13 @@ static int socket_manager_translate_events_nolock(
|
||||
|
||||
} else if (reg->polled[i].fd == sm->wait_sock) {
|
||||
|
||||
// TODO: consume
|
||||
// Consume one byte from the wakeup signal
|
||||
char byte;
|
||||
#ifdef _WIN32
|
||||
recv(sm->wait_sock, &byte, 1, 0);
|
||||
#else
|
||||
read(sm->wait_sock, &byte, 1);
|
||||
#endif
|
||||
|
||||
} else {
|
||||
|
||||
@@ -839,7 +869,8 @@ static int resolve_connect_targets(ConnectTarget *targets,
|
||||
name->data[targets[i].name.len] = '\0';
|
||||
char *hostname = name->data;
|
||||
#else
|
||||
char hostname[1<<9]; // TODO: Is this a good length?
|
||||
// 512 bytes is more than enough for a DNS hostname (max 253 chars)
|
||||
char hostname[1<<9];
|
||||
if (targets[i].name.len >= (int) sizeof(hostname))
|
||||
return -1;
|
||||
memcpy(hostname, targets[i].name.ptr, targets[i].name.len);
|
||||
@@ -848,8 +879,12 @@ static int resolve_connect_targets(ConnectTarget *targets,
|
||||
struct addrinfo *res = NULL;
|
||||
int ret = getaddrinfo(hostname, portstr, &hints, &res);
|
||||
if (ret != 0) {
|
||||
#ifdef HTTPS_ENABLED
|
||||
// Free the name allocated for this target
|
||||
free(name);
|
||||
#endif
|
||||
free_addr_list(resolved, num_resolved);
|
||||
return -1; // TODO: free all allocated registered names
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (struct addrinfo *rp = res; rp; rp = rp->ai_next) {
|
||||
@@ -1120,10 +1155,12 @@ int socket_close(SocketManager *sm, SocketHandle handle)
|
||||
if (s == NULL)
|
||||
ret = -1;
|
||||
else {
|
||||
// TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance?
|
||||
s->state = SOCKET_STATE_SHUTDOWN;
|
||||
s->events = 0;
|
||||
socket_update(s);
|
||||
// Only transition to SHUTDOWN if socket is not already DIED
|
||||
if (s->state != SOCKET_STATE_DIED) {
|
||||
s->state = SOCKET_STATE_SHUTDOWN;
|
||||
s->events = 0;
|
||||
socket_update(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (mutex_unlock(&sm->mutex) < 0)
|
||||
|
||||
Reference in New Issue
Block a user