enforce body size
This commit is contained in:
66
http.odin
66
http.odin
@@ -103,6 +103,14 @@ response_set_body :: proc(resp: ^HTTP_Response, data: []byte) {
|
||||
// Request handler function type
|
||||
Request_Handler :: #type proc(ctx: rawptr, request: ^HTTP_Request, request_alloc: mem.Allocator) -> HTTP_Response
|
||||
|
||||
// Parse error enum
|
||||
Parse_Error :: enum {
|
||||
None,
|
||||
Connection_Closed,
|
||||
Invalid_Request,
|
||||
Body_Too_Large,
|
||||
}
|
||||
|
||||
// Server configuration
|
||||
Server_Config :: struct {
|
||||
max_body_size: int, // default 100MB
|
||||
@@ -176,6 +184,9 @@ server_start :: proc(server: ^Server) -> bool {
|
||||
server.running = true
|
||||
|
||||
fmt.printfln("HTTP server listening on %v (thread-per-connection)", server.endpoint)
|
||||
fmt.printfln(" Max body size: %d MB", server.config.max_body_size / (1024 * 1024))
|
||||
fmt.printfln(" Max headers: %d", server.config.max_headers)
|
||||
fmt.printfln(" Keep-alive: %v", server.config.enable_keep_alive)
|
||||
|
||||
// Accept loop - spawn a thread for each connection
|
||||
for server.running {
|
||||
@@ -199,7 +210,6 @@ server_start :: proc(server: ^Server) -> bool {
|
||||
t.init_context = context
|
||||
t.data = conn_data
|
||||
thread.start(t)
|
||||
// Thread will clean itself up when done
|
||||
} else {
|
||||
// Failed to create thread, close connection
|
||||
net.close(conn)
|
||||
@@ -222,7 +232,7 @@ server_stop :: proc(server: ^Server) {
|
||||
|
||||
// Worker thread procedure
|
||||
connection_worker_thread :: proc(t: ^thread.Thread) {
|
||||
defer thread.destroy(t) // Clean up thread when done
|
||||
defer thread.destroy(t)
|
||||
|
||||
conn_data := cast(^Connection_Task_Data)t.data
|
||||
defer free(conn_data, conn_data.server.allocator)
|
||||
@@ -230,6 +240,15 @@ connection_worker_thread :: proc(t: ^thread.Thread) {
|
||||
handle_connection(conn_data.server, conn_data.conn, conn_data.source)
|
||||
}
|
||||
|
||||
// Create error response
|
||||
make_error_response_simple :: proc(allocator: mem.Allocator, status: HTTP_Status, message: string) -> HTTP_Response {
|
||||
response := response_init(allocator)
|
||||
response_set_status(&response, status)
|
||||
response_add_header(&response, "Content-Type", "text/plain")
|
||||
response_set_body(&response, transmute([]byte)message)
|
||||
return response
|
||||
}
|
||||
|
||||
// Handle a single connection
|
||||
handle_connection :: proc(server: ^Server, conn: net.TCP_Socket, source: net.Endpoint) {
|
||||
defer net.close(conn)
|
||||
@@ -253,8 +272,21 @@ handle_connection :: proc(server: ^Server, conn: net.TCP_Socket, source: net.End
|
||||
context.allocator = request_alloc
|
||||
defer context.allocator = old
|
||||
|
||||
request, parse_ok := parse_request(conn, request_alloc, server.config)
|
||||
if !parse_ok {
|
||||
request, parse_err := parse_request(conn, request_alloc, server.config)
|
||||
|
||||
// Handle parse errors
|
||||
if parse_err != .None {
|
||||
#partial switch parse_err {
|
||||
case .Body_Too_Large:
|
||||
// Send 413 Payload Too Large
|
||||
response := make_error_response_simple(request_alloc, .Payload_Too_Large,
|
||||
fmt.tprintf("Request body exceeds maximum size of %d bytes", server.config.max_body_size))
|
||||
send_response(conn, &response, request_alloc)
|
||||
case .Invalid_Request:
|
||||
// Send 400 Bad Request
|
||||
response := make_error_response_simple(request_alloc, .Bad_Request, "Invalid HTTP request")
|
||||
send_response(conn, &response, request_alloc)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
@@ -284,13 +316,13 @@ parse_request :: proc(
|
||||
conn: net.TCP_Socket,
|
||||
allocator: mem.Allocator,
|
||||
config: Server_Config,
|
||||
) -> (HTTP_Request, bool) {
|
||||
) -> (HTTP_Request, Parse_Error) {
|
||||
// Read request line and headers
|
||||
buffer := make([]byte, config.read_buffer_size, allocator)
|
||||
|
||||
bytes_read, read_err := net.recv_tcp(conn, buffer)
|
||||
if read_err != nil || bytes_read == 0 {
|
||||
return {}, false
|
||||
return {}, .Connection_Closed
|
||||
}
|
||||
|
||||
request_data := buffer[:bytes_read]
|
||||
@@ -298,7 +330,7 @@ parse_request :: proc(
|
||||
// Find end of headers (\r\n\r\n)
|
||||
header_end_idx := strings.index(string(request_data), "\r\n\r\n")
|
||||
if header_end_idx < 0 {
|
||||
return {}, false
|
||||
return {}, .Invalid_Request
|
||||
}
|
||||
|
||||
header_section := string(request_data[:header_end_idx])
|
||||
@@ -307,13 +339,13 @@ parse_request :: proc(
|
||||
// Parse request line
|
||||
lines := strings.split_lines(header_section, allocator)
|
||||
if len(lines) == 0 {
|
||||
return {}, false
|
||||
return {}, .Invalid_Request
|
||||
}
|
||||
|
||||
request_line := lines[0]
|
||||
parts := strings.split(request_line, " ", allocator)
|
||||
if len(parts) < 3 {
|
||||
return {}, false
|
||||
return {}, .Invalid_Request
|
||||
}
|
||||
|
||||
method := method_from_string(parts[0])
|
||||
@@ -339,6 +371,11 @@ parse_request :: proc(
|
||||
name = strings.clone(name, allocator),
|
||||
value = strings.clone(value, allocator),
|
||||
})
|
||||
|
||||
// Check max headers limit
|
||||
if len(headers) > config.max_headers {
|
||||
return {}, .Invalid_Request
|
||||
}
|
||||
}
|
||||
|
||||
// Read body if Content-Length present
|
||||
@@ -348,7 +385,12 @@ parse_request :: proc(
|
||||
if cl, ok := content_length_header.?; ok {
|
||||
content_length := strconv.parse_int(cl) or_else 0
|
||||
|
||||
if content_length > 0 && content_length <= config.max_body_size {
|
||||
// Check if body size exceeds limit
|
||||
if content_length > config.max_body_size {
|
||||
return {}, .Body_Too_Large
|
||||
}
|
||||
|
||||
if content_length > 0 {
|
||||
// Check if we already have the body in buffer
|
||||
existing_body := request_data[body_start:]
|
||||
|
||||
@@ -370,7 +412,7 @@ parse_request :: proc(
|
||||
|
||||
n, err := net.recv_tcp(conn, chunk)
|
||||
if err != nil || n == 0 {
|
||||
return {}, false
|
||||
return {}, .Connection_Closed
|
||||
}
|
||||
|
||||
copy(body[body_written:], chunk[:n])
|
||||
@@ -386,7 +428,7 @@ parse_request :: proc(
|
||||
path = path,
|
||||
headers = headers[:],
|
||||
body = body,
|
||||
}, true
|
||||
}, .None
|
||||
}
|
||||
|
||||
// Helper to get header from slice
|
||||
|
||||
Reference in New Issue
Block a user