diff --git a/src/decoder.ml b/src/decoder.ml index 70e048a..810af46 100644 --- a/src/decoder.ml +++ b/src/decoder.ml @@ -64,29 +64,39 @@ let header_field table max_size prefix prefix_length = (name, value) let rec header ({table; max_size_limit; max_field_size} as decoder) = - let* b = any_uint8 in - if b >= 32 && b < 64 then - let* max_size = any_int b 5 <* commit in - if max_size <= max_size_limit then begin - Dynamic_table.change_max_size table max_size; - header decoder - end else fail "exceeded size limit" - else if table.max_size > max_size_limit then - fail "exceeded size limit" - else if b >= 128 then - let* index = any_int b 7 in - match get_indexed_field table index with - | name, value -> return (Header.make name value) - | exception Invalid_index -> fail "invalid index" - else if b >= 64 then - let* (name, value) = header_field table max_field_size b 6 <* commit in - Dynamic_table.add table (name, value) |> ignore; - return (Header.make name value) - else - let* (name, value) = header_field table max_field_size b 4 <* commit in - return (Header.make ~never_index:(b >= 16) name value) + at_end_of_input >>= function + | true -> return None + | false -> + let* b = any_uint8 in + if b >= 32 && b < 64 then + let* max_size = any_int b 5 <* commit in + if max_size <= max_size_limit then begin + Dynamic_table.change_max_size table max_size; + header decoder + end else fail "exceeded size limit" + else if table.max_size > max_size_limit then + fail "exceeded size limit" + else if b >= 128 then + let* index = any_int b 7 <* commit in + match get_indexed_field table index with + | name, value -> return (Some (Header.make name value)) + | exception Invalid_index -> fail "invalid index" + else if b >= 64 then + let* (name, value) = header_field table max_field_size b 6 <* commit in + Dynamic_table.add table (name, value) |> ignore; + return (Some (Header.make name value)) + else + let* (name, value) = header_field table max_field_size b 4 <* commit in + return (Some (Header.make ~never_index:(b >= 16) name value)) -let headers t = many (header t) <* end_of_input +let headers t = + fix begin fun m -> + header t >>= function + | Some h -> + let* hs = m in + return (h :: hs) + | None -> return [] + end let change_table_size_limit ({table; _} as decoder) max_size_limit = if max_size_limit < 0 then diff --git a/src/decoder.mli b/src/decoder.mli index f167932..da5d697 100644 --- a/src/decoder.mli +++ b/src/decoder.mli @@ -9,7 +9,7 @@ val create : ?max_size_limit:int -> ?max_field_size:int -> unit -> t field. The default is 4096. *) -val header : t -> Header.t Angstrom.t +val header : t -> Header.t option Angstrom.t (** A parser for one header *) val headers : t -> Header.t list Angstrom.t diff --git a/test/test.ml b/test/test.ml index 9fa92e6..1b0c472 100644 --- a/test/test.ml +++ b/test/test.ml @@ -25,25 +25,28 @@ let rec headers_of_entries = function | Size _ :: entries -> headers_of_entries entries | [] -> [] -let test entries s = [ - "Encode", `Quick, begin fun () -> - let t = Faraday.create 100 in - let encoder = Encoder.create () in - entries |> List.iter begin function - | Header header -> Encoder.encode_header encoder t header - | Size size -> Encoder.change_table_size encoder size - end; - let s' = Faraday.serialize_to_string t in - Alcotest.(check encoding) "same encoding" s s' - end; - "Decode", `Quick, begin fun () -> - let decoder = Decoder.create () in - Alcotest.(check (result (list header) reject)) - "same headers" - (Angstrom.parse_string (Decoder.headers decoder) s) - (Ok (headers_of_entries entries)) - end; -] +let test_encode entries s = + ("Encode", `Quick, begin fun () -> + let t = Faraday.create 100 in + let encoder = Encoder.create () in + entries |> List.iter begin function + | Header header -> Encoder.encode_header encoder t header + | Size size -> Encoder.change_table_size encoder size + end; + let s' = Faraday.serialize_to_string t in + Alcotest.(check encoding) "same encoding" s s' + end) + +let test_decode entries s = + ("Decode", `Quick, begin fun () -> + let decoder = Decoder.create () in + Alcotest.(check (result (list header) reject)) + "same headers" + (Angstrom.parse_string (Decoder.headers decoder) s) + (Ok (headers_of_entries entries)) + end) + +let test entries s = [test_encode entries s; test_decode entries s] let test_static = let entries = [ @@ -126,6 +129,13 @@ let test_eviction = \x40\x04ABCD\x03XYZ" in test entries s +let test_empty = test [] "" + +let test_resize_only = [ + test_encode [Size 0] ""; + test_decode [Size 0] "\x20" +] + let () = Alcotest.run "Hpack" [ "Static Indexing", test_static; (* RFC7541§2.3.1 *) @@ -135,4 +145,6 @@ let () = "Never Index", test_never_index; (* RFC7541§7.1.3 *) "Table Size Update", test_size_update; (* RFC7541§4.2 *) "Entry Eviction", test_eviction; (* RFC7541§4.4 *) + "Empty header list", test_empty; + "Empty header list with table size update", test_resize_only; ]