An OCaml webserver, but the allocating version (vs httpz which doesnt)
at main 131 lines 3.8 kB view raw
1(* buf_read.ml - Buffer type and utilities for HTTP parsing *) 2 3type status = 4 | Complete 5 | Partial 6 | Invalid_method 7 | Invalid_target 8 | Invalid_version 9 | Invalid_header 10 | Headers_too_large 11 | Malformed 12 | Content_length_overflow (* Content-Length value too large or invalid *) 13 | Ambiguous_framing (* Both Content-Length and Transfer-Encoding present *) 14 | Bare_cr_detected (* CR without LF - HTTP smuggling attempt *) 15 | Missing_host_header (* HTTP/1.1 requires Host header *) 16 | Unsupported_transfer_encoding (* Transfer-Encoding other than chunked/identity *) 17 18let status_to_string = function 19 | Complete -> "Complete" 20 | Partial -> "Partial" 21 | Invalid_method -> "Invalid_method" 22 | Invalid_target -> "Invalid_target" 23 | Invalid_version -> "Invalid_version" 24 | Invalid_header -> "Invalid_header" 25 | Headers_too_large -> "Headers_too_large" 26 | Malformed -> "Malformed" 27 | Content_length_overflow -> "Content_length_overflow" 28 | Ambiguous_framing -> "Ambiguous_framing" 29 | Bare_cr_detected -> "Bare_cr_detected" 30 | Missing_host_header -> "Missing_host_header" 31 | Unsupported_transfer_encoding -> "Unsupported_transfer_encoding" 32;; 33 34let pp_status fmt t = Stdlib.Format.fprintf fmt "%s" (status_to_string t) 35 36open Base 37 38let buffer_size = 32768 39let max_headers = 32 40 41let create () = Base_bigstring.create buffer_size 42let peek buf pos = Base_bigstring.unsafe_get buf pos 43 44let is_token_char c = 45 match c with 46 | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> true 47 | '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '.' -> true 48 | '^' | '_' | '`' | '|' | '~' -> true 49 | _ -> false 50;; 51 52let is_space c = 53 match c with 54 | ' ' | '\t' -> true 55 | _ -> false 56;; 57 58let to_lower c = 59 match c with 60 | 'A' .. 'Z' -> Char.of_int_exn (Char.to_int c + 32) 61 | _ -> c 62;; 63 64let find_crlf buf ~pos ~len = 65 if len - pos < 2 66 then -1 67 else ( 68 let p = ref pos in 69 let found = ref false in 70 while (not !found) && !p + 1 < len do 71 let search_pos = !p in 72 let search_len = len - !p in 73 let cr_pos = Base_bigstring.unsafe_find buf '\r' ~pos:search_pos ~len:search_len in 74 if cr_pos < 0 || cr_pos >= len - 1 75 then p := len 76 else if Char.equal (Base_bigstring.unsafe_get buf (cr_pos + 1)) '\n' 77 then ( 78 p := cr_pos; 79 found := true) 80 else p := cr_pos + 1 81 done; 82 if !found then !p else -1) 83;; 84 85let pp fmt _t = Stdlib.Format.fprintf fmt "<buffer %d bytes>" buffer_size 86 87(* Security limits - configurable per-server *) 88type limits = 89 { max_content_length : int64 (* Default: 100MB *) 90 ; max_header_size : int (* Default: 16KB - size of all headers combined *) 91 ; max_header_count : int (* Default: 100 *) 92 ; max_chunk_size : int (* Default: 16MB *) 93 } 94 95let default_limits = 96 { max_content_length = 104857600L (* 100MB *) 97 ; max_header_size = 16384 (* 16KB *) 98 ; max_header_count = 100 99 ; max_chunk_size = 16777216 (* 16MB *) 100 } 101 102(* Detect bare CR (CR not followed by LF) - RFC 7230 Section 3.5 103 Used to prevent HTTP request smuggling attacks *) 104let has_bare_cr buf ~pos ~len = 105 let end_pos = pos + len in 106 let p = ref pos in 107 let found = ref false in 108 while (not !found) && !p < end_pos do 109 if Char.equal (peek buf !p) '\r' then ( 110 if !p + 1 >= end_pos || not (Char.equal (peek buf (!p + 1)) '\n') then 111 found := true 112 else 113 p := !p + 2 (* Skip past valid CRLF *) 114 ) else 115 Int.incr p 116 done; 117 !found 118;; 119 120(* Check if a value contains CRLF injection attempt *) 121let has_crlf_injection buf ~pos ~len = 122 let end_pos = pos + len in 123 let p = ref pos in 124 let found = ref false in 125 while (not !found) && !p < end_pos do 126 match peek buf !p with 127 | '\r' | '\n' -> found := true 128 | _ -> Int.incr p 129 done; 130 !found 131;;