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 function type
|
||||||
Request_Handler :: #type proc(ctx: rawptr, request: ^HTTP_Request, request_alloc: mem.Allocator) -> HTTP_Response
|
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 configuration
|
||||||
Server_Config :: struct {
|
Server_Config :: struct {
|
||||||
max_body_size: int, // default 100MB
|
max_body_size: int, // default 100MB
|
||||||
@@ -176,6 +184,9 @@ server_start :: proc(server: ^Server) -> bool {
|
|||||||
server.running = true
|
server.running = true
|
||||||
|
|
||||||
fmt.printfln("HTTP server listening on %v (thread-per-connection)", server.endpoint)
|
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
|
// Accept loop - spawn a thread for each connection
|
||||||
for server.running {
|
for server.running {
|
||||||
@@ -199,7 +210,6 @@ server_start :: proc(server: ^Server) -> bool {
|
|||||||
t.init_context = context
|
t.init_context = context
|
||||||
t.data = conn_data
|
t.data = conn_data
|
||||||
thread.start(t)
|
thread.start(t)
|
||||||
// Thread will clean itself up when done
|
|
||||||
} else {
|
} else {
|
||||||
// Failed to create thread, close connection
|
// Failed to create thread, close connection
|
||||||
net.close(conn)
|
net.close(conn)
|
||||||
@@ -222,7 +232,7 @@ server_stop :: proc(server: ^Server) {
|
|||||||
|
|
||||||
// Worker thread procedure
|
// Worker thread procedure
|
||||||
connection_worker_thread :: proc(t: ^thread.Thread) {
|
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
|
conn_data := cast(^Connection_Task_Data)t.data
|
||||||
defer free(conn_data, conn_data.server.allocator)
|
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)
|
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 a single connection
|
||||||
handle_connection :: proc(server: ^Server, conn: net.TCP_Socket, source: net.Endpoint) {
|
handle_connection :: proc(server: ^Server, conn: net.TCP_Socket, source: net.Endpoint) {
|
||||||
defer net.close(conn)
|
defer net.close(conn)
|
||||||
@@ -253,8 +272,21 @@ handle_connection :: proc(server: ^Server, conn: net.TCP_Socket, source: net.End
|
|||||||
context.allocator = request_alloc
|
context.allocator = request_alloc
|
||||||
defer context.allocator = old
|
defer context.allocator = old
|
||||||
|
|
||||||
request, parse_ok := parse_request(conn, request_alloc, server.config)
|
request, parse_err := parse_request(conn, request_alloc, server.config)
|
||||||
if !parse_ok {
|
|
||||||
|
// 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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,13 +316,13 @@ parse_request :: proc(
|
|||||||
conn: net.TCP_Socket,
|
conn: net.TCP_Socket,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
config: Server_Config,
|
config: Server_Config,
|
||||||
) -> (HTTP_Request, bool) {
|
) -> (HTTP_Request, Parse_Error) {
|
||||||
// Read request line and headers
|
// Read request line and headers
|
||||||
buffer := make([]byte, config.read_buffer_size, allocator)
|
buffer := make([]byte, config.read_buffer_size, allocator)
|
||||||
|
|
||||||
bytes_read, read_err := net.recv_tcp(conn, buffer)
|
bytes_read, read_err := net.recv_tcp(conn, buffer)
|
||||||
if read_err != nil || bytes_read == 0 {
|
if read_err != nil || bytes_read == 0 {
|
||||||
return {}, false
|
return {}, .Connection_Closed
|
||||||
}
|
}
|
||||||
|
|
||||||
request_data := buffer[:bytes_read]
|
request_data := buffer[:bytes_read]
|
||||||
@@ -298,7 +330,7 @@ parse_request :: proc(
|
|||||||
// Find end of headers (\r\n\r\n)
|
// Find end of headers (\r\n\r\n)
|
||||||
header_end_idx := strings.index(string(request_data), "\r\n\r\n")
|
header_end_idx := strings.index(string(request_data), "\r\n\r\n")
|
||||||
if header_end_idx < 0 {
|
if header_end_idx < 0 {
|
||||||
return {}, false
|
return {}, .Invalid_Request
|
||||||
}
|
}
|
||||||
|
|
||||||
header_section := string(request_data[:header_end_idx])
|
header_section := string(request_data[:header_end_idx])
|
||||||
@@ -307,13 +339,13 @@ parse_request :: proc(
|
|||||||
// Parse request line
|
// Parse request line
|
||||||
lines := strings.split_lines(header_section, allocator)
|
lines := strings.split_lines(header_section, allocator)
|
||||||
if len(lines) == 0 {
|
if len(lines) == 0 {
|
||||||
return {}, false
|
return {}, .Invalid_Request
|
||||||
}
|
}
|
||||||
|
|
||||||
request_line := lines[0]
|
request_line := lines[0]
|
||||||
parts := strings.split(request_line, " ", allocator)
|
parts := strings.split(request_line, " ", allocator)
|
||||||
if len(parts) < 3 {
|
if len(parts) < 3 {
|
||||||
return {}, false
|
return {}, .Invalid_Request
|
||||||
}
|
}
|
||||||
|
|
||||||
method := method_from_string(parts[0])
|
method := method_from_string(parts[0])
|
||||||
@@ -339,6 +371,11 @@ parse_request :: proc(
|
|||||||
name = strings.clone(name, allocator),
|
name = strings.clone(name, allocator),
|
||||||
value = strings.clone(value, 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
|
// Read body if Content-Length present
|
||||||
@@ -348,7 +385,12 @@ parse_request :: proc(
|
|||||||
if cl, ok := content_length_header.?; ok {
|
if cl, ok := content_length_header.?; ok {
|
||||||
content_length := strconv.parse_int(cl) or_else 0
|
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
|
// Check if we already have the body in buffer
|
||||||
existing_body := request_data[body_start:]
|
existing_body := request_data[body_start:]
|
||||||
|
|
||||||
@@ -370,7 +412,7 @@ parse_request :: proc(
|
|||||||
|
|
||||||
n, err := net.recv_tcp(conn, chunk)
|
n, err := net.recv_tcp(conn, chunk)
|
||||||
if err != nil || n == 0 {
|
if err != nil || n == 0 {
|
||||||
return {}, false
|
return {}, .Connection_Closed
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(body[body_written:], chunk[:n])
|
copy(body[body_written:], chunk[:n])
|
||||||
@@ -386,7 +428,7 @@ parse_request :: proc(
|
|||||||
path = path,
|
path = path,
|
||||||
headers = headers[:],
|
headers = headers[:],
|
||||||
body = body,
|
body = body,
|
||||||
}, true
|
}, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get header from slice
|
// Helper to get header from slice
|
||||||
|
|||||||
Reference in New Issue
Block a user