diff --git a/Makefile b/Makefile index 60530672..c2a1be12 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,9 @@ build: test: @dune runtest --no-buffer --force $(OPTS) +test-autopromote: + @dune runtest --no-buffer --force $(OPTS) --auto-promote + clean: @dune clean diff --git a/src/core/IO.ml b/src/core/IO.ml index 3307537a..138668c4 100644 --- a/src/core/IO.ml +++ b/src/core/IO.ml @@ -348,7 +348,14 @@ module Input = struct method private refill (slice : Slice.t) : unit = if !chunk_size = 0 && not !eof then ( chunk_size := read_next_chunk_len (); - if !chunk_size = 0 then eof := true (* stream is finished *) + if !chunk_size = 0 then ( + (* stream is finished, consume trailing \r\n *) + eof := true; + let line = read_line_using ~buf:line_buf ic in + if String.trim line <> "" then + raise + (fail (spf "expected \\r\\n to follow last chunk, got %S" line)) + ) ); slice.off <- 0; slice.len <- 0; diff --git a/tests/echo1.expect b/tests/echo1.expect index 7a51e034..e0fa89b2 100644 --- a/tests/echo1.expect +++ b/tests/echo1.expect @@ -50,7 +50,7 @@ test_out.txt hello world -ykjNycnnKs8vyknhAgAAAP//AwA= +ykjNycnnKs8vyknhAgAAAP// diff --git a/tests/echo1.sh b/tests/echo1.sh index 4c4dec92..0c5c6948 100755 --- a/tests/echo1.sh +++ b/tests/echo1.sh @@ -15,7 +15,10 @@ sleep 0.1 curl -N "http://localhost:${PORT}/vfs/a.txt" --max-time 5 sleep 0.1 -curl -N "http://localhost:${PORT}/vfs/a.txt" -H 'accept-encoding: deflate' --max-time 5 | base64 +# NOTE: the sed is there because of a timing/deflate non determinism. Both strings +# decompress to the same "hello\nworld\n" but which one is picked depends on +# the machine/library/… ?? but both are valid. +curl -N "http://localhost:${PORT}/vfs/a.txt" -H 'accept-encoding: deflate' --max-time 5 | base64 | sed 's+ykjNycnnKs8vyknhAgAAAP//AwA=+ykjNycnnKs8vyknhAgAAAP//+' sleep 0.1 curl -N "http://localhost:${PORT}/vfs/sub/yolo.html" --max-time 5 diff --git a/tests/unit/dune b/tests/unit/dune index 7be0c4d9..b4ceef08 100644 --- a/tests/unit/dune +++ b/tests/unit/dune @@ -1,5 +1,5 @@ (tests - (names t_util t_buf t_server) + (names t_util t_buf t_server t_io) (package tiny_httpd) (libraries tiny_httpd.core qcheck-core qcheck-core.runner test_util)) diff --git a/tests/unit/t_io.ml b/tests/unit/t_io.ml new file mode 100644 index 00000000..815831d6 --- /dev/null +++ b/tests/unit/t_io.ml @@ -0,0 +1,42 @@ +open Test_util +open Tiny_httpd_core.IO + +let spf = Printf.sprintf + +(* one chunk *) +let () = + let io = Input.of_string "5\r\nhello\r\n0\r\n\r\nARGH" in + let str = + io + |> Input.read_chunked ~bytes:(Bytes.create 4) ~fail:failwith + |> Input.read_all_using ~buf:(Buf.create ()) + in + assert_eq ~to_string:(spf "%S") "hello" str + +(* two chunks *) +let () = + let io = Input.of_string "5\r\nhello\r\n6\r\n world\r\n0\r\n\r\nARGH" in + let str = + io + |> Input.read_chunked ~bytes:(Bytes.create 4) ~fail:failwith + |> Input.read_all_using ~buf:(Buf.create ()) + in + assert_eq ~to_string:(spf "%S") "hello world" str; + + let str_rest = io |> Input.read_all_using ~buf:(Buf.create ()) in + assert_eq ~to_string:(spf "%S") "ARGH" str_rest; + () + +(* two chunks *) +let () = + let io = Input.of_string "10\r\n{\"poCheck\":true}\r\n0\r\n\r\n" in + let str = + io + |> Input.read_chunked ~bytes:(Bytes.create 4) ~fail:failwith + |> Input.read_all_using ~buf:(Buf.create ()) + in + assert_eq ~to_string:(spf "%S") {|{"poCheck":true}|} str; + + let str_rest = io |> Input.read_all_using ~buf:(Buf.create ()) in + assert_eq ~to_string:(spf "%S") "" str_rest; + ()