mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-06 03:05:29 -05:00
remove some uses of scanf in parsing
This commit is contained in:
parent
5018df5ff8
commit
da55098a7a
8 changed files with 126 additions and 9 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
(** Tiny Http Server
|
(** Tiny Http Server
|
||||||
|
|
||||||
This library implements a very simple, basic HTTP/1.1 server using blocking
|
This library implements a very simple, basic HTTP/1.1 server using blocking
|
||||||
IOs and threads. Basic routing based on {!Scanf} is provided for convenience,
|
IOs and threads. Basic routing based is provided for convenience,
|
||||||
so that several handlers can be registered.
|
so that several handlers can be registered.
|
||||||
|
|
||||||
It is possible to use a thread pool, see {!create}'s argument [new_thread].
|
It is possible to use a thread pool, see {!create}'s argument [new_thread].
|
||||||
|
|
|
||||||
77
src/Tiny_httpd_parse_.ml
Normal file
77
src/Tiny_httpd_parse_.ml
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
(** Basic parser for lines *)
|
||||||
|
|
||||||
|
type 'a t = string -> int ref -> 'a
|
||||||
|
|
||||||
|
open struct
|
||||||
|
let spf = Printf.sprintf
|
||||||
|
end
|
||||||
|
|
||||||
|
let[@inline] eof s off = !off = String.length s
|
||||||
|
|
||||||
|
let[@inline] skip_space : unit t =
|
||||||
|
fun s off ->
|
||||||
|
while !off < String.length s && String.unsafe_get s !off = ' ' do
|
||||||
|
incr off
|
||||||
|
done
|
||||||
|
|
||||||
|
let pos_int : int t =
|
||||||
|
fun s off : int ->
|
||||||
|
skip_space s off;
|
||||||
|
let n = ref 0 in
|
||||||
|
let continue = ref true in
|
||||||
|
while !off < String.length s && !continue do
|
||||||
|
match String.unsafe_get s !off with
|
||||||
|
| '0' .. '9' as c -> n := (!n * 10) + Char.code c - Char.code '0'
|
||||||
|
| ' ' | '\t' | '\n' -> continue := false
|
||||||
|
| c -> failwith @@ spf "expected int, got %C" c
|
||||||
|
done;
|
||||||
|
!n
|
||||||
|
|
||||||
|
let pos_hex : int t =
|
||||||
|
fun s off : int ->
|
||||||
|
skip_space s off;
|
||||||
|
let n = ref 0 in
|
||||||
|
let continue = ref true in
|
||||||
|
while !off < String.length s && !continue do
|
||||||
|
match String.unsafe_get s !off with
|
||||||
|
| 'a' .. 'f' as c ->
|
||||||
|
incr off;
|
||||||
|
n := (!n * 16) + Char.code c - Char.code 'a' + 10
|
||||||
|
| 'A' .. 'F' as c ->
|
||||||
|
incr off;
|
||||||
|
n := (!n * 16) + Char.code c - Char.code 'A' + 10
|
||||||
|
| '0' .. '9' as c ->
|
||||||
|
incr off;
|
||||||
|
n := (!n * 16) + Char.code c - Char.code '0'
|
||||||
|
| ' ' | '\r' -> continue := false
|
||||||
|
| c -> failwith @@ spf "expected int, got %C" c
|
||||||
|
done;
|
||||||
|
!n
|
||||||
|
|
||||||
|
(** Parse a word without spaces *)
|
||||||
|
let word : string t =
|
||||||
|
fun s off ->
|
||||||
|
skip_space s off;
|
||||||
|
let start = !off in
|
||||||
|
let continue = ref true in
|
||||||
|
while !off < String.length s && !continue do
|
||||||
|
match String.unsafe_get s !off with
|
||||||
|
| ' ' | '\r' -> continue := false
|
||||||
|
| _ -> incr off
|
||||||
|
done;
|
||||||
|
if !off = start then failwith "expected word";
|
||||||
|
String.sub s start (!off - start)
|
||||||
|
|
||||||
|
let exact str : unit t =
|
||||||
|
fun s off ->
|
||||||
|
skip_space s off;
|
||||||
|
let len = String.length str in
|
||||||
|
if !off + len > String.length s then
|
||||||
|
failwith @@ spf "unexpected EOF, expected %S" str;
|
||||||
|
for i = 0 to len - 1 do
|
||||||
|
let expected = String.unsafe_get str i in
|
||||||
|
let c = String.unsafe_get s (!off + i) in
|
||||||
|
if c <> expected then
|
||||||
|
failwith @@ spf "expected %S, got %C at position %d" str c i
|
||||||
|
done;
|
||||||
|
off := !off + len
|
||||||
|
|
@ -173,6 +173,9 @@ module Request = struct
|
||||||
let query self = self.query
|
let query self = self.query
|
||||||
let get_header ?f self h = Headers.get ?f h self.headers
|
let get_header ?f self h = Headers.get ?f h self.headers
|
||||||
|
|
||||||
|
let remove_header k self =
|
||||||
|
{ self with headers = Headers.remove k self.headers }
|
||||||
|
|
||||||
let get_header_int self h =
|
let get_header_int self h =
|
||||||
match get_header self h with
|
match get_header self h with
|
||||||
| Some x -> (try Some (int_of_string x) with _ -> None)
|
| Some x -> (try Some (int_of_string x) with _ -> None)
|
||||||
|
|
@ -243,12 +246,22 @@ module Request = struct
|
||||||
let start_time = get_time_s () in
|
let start_time = get_time_s () in
|
||||||
let meth, path, version =
|
let meth, path, version =
|
||||||
try
|
try
|
||||||
let meth, path, version =
|
let off = ref 0 in
|
||||||
Scanf.sscanf line "%s %s HTTP/1.%d\r" (fun x y z -> x, y, z)
|
let meth = Tiny_httpd_parse_.word line off in
|
||||||
|
let path = Tiny_httpd_parse_.word line off in
|
||||||
|
let http_version = Tiny_httpd_parse_.word line off in
|
||||||
|
let version =
|
||||||
|
match http_version with
|
||||||
|
| "HTTP/1.1" -> 1
|
||||||
|
| "HTTP/1.0" -> 0
|
||||||
|
| v -> invalid_arg (Printf.sprintf "unsupported HTTP version: %s" v)
|
||||||
in
|
in
|
||||||
if version != 0 && version != 1 then raise Exit;
|
|
||||||
meth, path, version
|
meth, path, version
|
||||||
with _ ->
|
with
|
||||||
|
| Invalid_argument msg ->
|
||||||
|
Log.error (fun k -> k "invalid request line: `%s`: %s" line msg);
|
||||||
|
raise (Bad_req (400, "Invalid request line"))
|
||||||
|
| _ ->
|
||||||
Log.error (fun k -> k "invalid request line: `%s`" line);
|
Log.error (fun k -> k "invalid request line: `%s`" line);
|
||||||
raise (Bad_req (400, "Invalid request line"))
|
raise (Bad_req (400, "Invalid request line"))
|
||||||
in
|
in
|
||||||
|
|
@ -354,6 +367,10 @@ module Response = struct
|
||||||
let set_headers headers self = { self with headers }
|
let set_headers headers self = { self with headers }
|
||||||
let update_headers f self = { self with headers = f self.headers }
|
let update_headers f self = { self with headers = f self.headers }
|
||||||
let set_header k v self = { self with headers = Headers.set k v self.headers }
|
let set_header k v self = { self with headers = Headers.set k v self.headers }
|
||||||
|
|
||||||
|
let remove_header k self =
|
||||||
|
{ self with headers = Headers.remove k self.headers }
|
||||||
|
|
||||||
let set_code code self = { self with code }
|
let set_code code self = { self with code }
|
||||||
|
|
||||||
let make_raw ?(headers = []) ~code body : t =
|
let make_raw ?(headers = []) ~code body : t =
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,10 @@ module Request : sig
|
||||||
val set_header : string -> string -> 'a t -> 'a t
|
val set_header : string -> string -> 'a t -> 'a t
|
||||||
(** [set_header k v req] sets [k: v] in the request [req]'s headers. *)
|
(** [set_header k v req] sets [k: v] in the request [req]'s headers. *)
|
||||||
|
|
||||||
|
val remove_header : string -> 'a t -> 'a t
|
||||||
|
(** Remove one instance of this header.
|
||||||
|
@since NEXT_RELEASE *)
|
||||||
|
|
||||||
val update_headers : (Headers.t -> Headers.t) -> 'a t -> 'a t
|
val update_headers : (Headers.t -> Headers.t) -> 'a t -> 'a t
|
||||||
(** Modify headers using the given function.
|
(** Modify headers using the given function.
|
||||||
@since 0.11 *)
|
@since 0.11 *)
|
||||||
|
|
@ -243,6 +247,10 @@ module Response : sig
|
||||||
(** Modify headers.
|
(** Modify headers.
|
||||||
@since 0.11 *)
|
@since 0.11 *)
|
||||||
|
|
||||||
|
val remove_header : string -> t -> t
|
||||||
|
(** Remove one instance of this header.
|
||||||
|
@since NEXT_RELEASE *)
|
||||||
|
|
||||||
val set_headers : Headers.t -> t -> t
|
val set_headers : Headers.t -> t -> t
|
||||||
(** Set all headers.
|
(** Set all headers.
|
||||||
@since 0.11 *)
|
@since 0.11 *)
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,10 @@ let read_chunked ?(buf = Buf.create ()) ~fail (bs : t) : t =
|
||||||
if String.trim line = "" then
|
if String.trim line = "" then
|
||||||
0
|
0
|
||||||
else (
|
else (
|
||||||
try Scanf.sscanf line "%x %s@\r" (fun n _ext -> n)
|
try
|
||||||
|
let off = ref 0 in
|
||||||
|
let n = Tiny_httpd_parse_.pos_hex line off in
|
||||||
|
n
|
||||||
with _ ->
|
with _ ->
|
||||||
raise (fail (spf "cannot read chunk size from line %S" line))
|
raise (fail (spf "cannot read chunk size from line %S" line))
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ let percent_encode ?(skip = fun _ -> false) s =
|
||||||
s;
|
s;
|
||||||
Buffer.contents buf
|
Buffer.contents buf
|
||||||
|
|
||||||
let hex_int (s : string) : int = Scanf.sscanf s "%x" (fun x -> x)
|
let int_of_hex_nibble = function
|
||||||
|
| '0' .. '9' as c -> Char.code c - Char.code '0'
|
||||||
|
| 'a' .. 'f' as c -> 10 + Char.code c - Char.code 'a'
|
||||||
|
| 'A' .. 'F' as c -> 10 + Char.code c - Char.code 'A'
|
||||||
|
| _ -> invalid_arg "string: invalid hex"
|
||||||
|
|
||||||
let percent_decode (s : string) : _ option =
|
let percent_decode (s : string) : _ option =
|
||||||
let buf = Buffer.create (String.length s) in
|
let buf = Buffer.create (String.length s) in
|
||||||
|
|
@ -21,7 +25,10 @@ let percent_decode (s : string) : _ option =
|
||||||
match String.get s !i with
|
match String.get s !i with
|
||||||
| '%' ->
|
| '%' ->
|
||||||
if !i + 2 < String.length s then (
|
if !i + 2 < String.length s then (
|
||||||
(match hex_int @@ String.sub s (!i + 1) 2 with
|
(match
|
||||||
|
(int_of_hex_nibble (String.get s (!i + 1)) lsl 4)
|
||||||
|
+ int_of_hex_nibble (String.get s (!i + 2))
|
||||||
|
with
|
||||||
| n -> Buffer.add_char buf (Char.chr n)
|
| n -> Buffer.add_char buf (Char.chr n)
|
||||||
| exception _ -> raise Exit);
|
| exception _ -> raise Exit);
|
||||||
i := !i + 3
|
i := !i + 3
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,11 @@ let decompress_req_stream_ ~buf_size (req : BS.t S.Request.t) : _ S.Request.t =
|
||||||
let req' = S.Request.set_header req "Transfer-Encoding" "chunked" in
|
let req' = S.Request.set_header req "Transfer-Encoding" "chunked" in
|
||||||
Some (req', decode_gzip_stream_)
|
Some (req', decode_gzip_stream_)
|
||||||
*)
|
*)
|
||||||
|
| Some "deflate" ->
|
||||||
|
let body' = S.Request.body req |> decode_deflate_stream_ ~buf_size in
|
||||||
|
req
|
||||||
|
|> S.Request.remove_header "Transfer-Encoding"
|
||||||
|
|> S.Request.set_body body'
|
||||||
| Some s when has_deflate s ->
|
| Some s when has_deflate s ->
|
||||||
(match Scanf.sscanf s "deflate, %s" (fun s -> s) with
|
(match Scanf.sscanf s "deflate, %s" (fun s -> s) with
|
||||||
| tr' ->
|
| tr' ->
|
||||||
|
|
|
||||||
2
src/dune
2
src/dune
|
|
@ -1,7 +1,7 @@
|
||||||
(library
|
(library
|
||||||
(name tiny_httpd)
|
(name tiny_httpd)
|
||||||
(public_name tiny_httpd)
|
(public_name tiny_httpd)
|
||||||
(private_modules Tiny_httpd_mime_)
|
(private_modules Tiny_httpd_mime_ Tiny_httpd_parse_)
|
||||||
(libraries threads seq unix
|
(libraries threads seq unix
|
||||||
(select Tiny_httpd_mime_.ml from
|
(select Tiny_httpd_mime_.ml from
|
||||||
(magic-mime -> Tiny_httpd_mime_.magic.ml)
|
(magic-mime -> Tiny_httpd_mime_.magic.ml)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue