fix(zip): handle case where camlzip consumes 0 bytes

This commit is contained in:
Simon Cruanes 2020-05-27 20:41:53 -04:00
parent 4300e8dcf0
commit 4a83a2f07f
3 changed files with 69 additions and 40 deletions

View file

@ -102,7 +102,7 @@ module Byte_stream = struct
ref ( ref (
match len with match len with
| Some n -> | Some n ->
if n > Bytes.length s -i then invalid_arg "Byte_stream.of_bytes"; if n > Bytes.length s - i then invalid_arg "Byte_stream.of_bytes";
n n
| None -> Bytes.length s - i | None -> Bytes.length s - i
) )
@ -110,7 +110,7 @@ module Byte_stream = struct
let i = ref i in let i = ref i in
{ bs_fill_buf=(fun () -> s, !i, !len); { bs_fill_buf=(fun () -> s, !i, !len);
bs_close=(fun () -> len := 0); bs_close=(fun () -> len := 0);
bs_consume=(fun n -> assert (n<= !len); i := !i + n; len := !len - n); bs_consume=(fun n -> assert (n>=0 && n<= !len); i := !i + n; len := !len - n);
} }
let of_string s : t = let of_string s : t =

View file

@ -64,38 +64,59 @@ let encode_deflate_stream_ ~buf_size (is:S.byte_stream) : S.byte_stream =
write_offset := n + !write_offset write_offset := n + !write_offset
in in
let bs_fill_buf () = let bs_fill_buf () =
S._debug (fun k->k "deflate.fill buf"); let rec loop() =
if !write_offset >= !buf_len then ( S._debug (fun k->k "deflate.fill.iter out_off=%d out_len=%d"
(* see if we need to refill *) !write_offset !buf_len);
write_offset := 0; if !write_offset < !buf_len then (
buf_len := 0; (* still the same slice, not consumed entirely by output *)
if !refill then ( buf, !write_offset, !buf_len - !write_offset
) else if not !refill then (
(* empty slice, no refill *)
buf, !write_offset, !buf_len - !write_offset
) else (
(* the output was entirely consumed, we need to do more work *)
write_offset := 0;
buf_len := 0;
let in_s, in_i, in_len = is.S.bs_fill_buf () in let in_s, in_i, in_len = is.S.bs_fill_buf () in
begin if in_len>0 then (
try (* try to decompress from input buffer *)
(* decompress from input buffer *) let _finished, used_in, used_out =
let finished, used_in, used_out = Zlib.deflate zlib_str
Zlib.deflate zlib_str in_s in_i in_len
in_s in_i in_len buf 0 (Bytes.length buf)
buf 0 (Bytes.length buf) Zlib.Z_NO_FLUSH
Zlib.Z_SYNC_FLUSH in
in buf_len := used_out;
buf_len := used_out; is.S.bs_consume used_in;
is.S.bs_consume used_in; S._debug
S._debug (fun k->k "encode %d bytes as %d bytes using deflate (finished: %b)"
(fun k->k "encode %d bytes as %d bytes using deflate (finished: %b)" used_in used_out _finished);
used_in used_out finished); if _finished then (
if finished then ( S._debug (fun k->k "deflate: finished");
S._debug (fun k->k "deflate: finished"); refill := false;
refill := false; );
); loop()
with Zlib.Error (e1,e2) -> ) else (
S.Response.fail_raise ~code:400 (* finish sending the internal state *)
"deflate: error during compression:\n%s %s" e1 e2 let _finished, used_in, used_out =
end; Zlib.deflate zlib_str
); in_s in_i in_len
); buf 0 (Bytes.length buf)
buf, !write_offset, !buf_len - !write_offset Zlib.Z_FULL_FLUSH
in
assert (used_in = 0);
buf_len := used_out;
if used_out = 0 then (
refill := false;
);
loop()
)
)
in
try loop()
with Zlib.Error (e1,e2) ->
S.Response.fail_raise ~code:400
"deflate: error during compression:\n%s %s" e1 e2
in in
{S.bs_fill_buf; bs_consume; bs_close} {S.bs_fill_buf; bs_consume; bs_close}
@ -141,22 +162,25 @@ let cb_decode_compressed_stream ~buf_size (req:unit S.Request.t) : _ option =
| _ -> None | _ -> None
let cb_encode_compressed_stream let cb_encode_compressed_stream
~compress_above
~buf_size (req:_ S.Request.t) (resp:S.Response.t) : _ option = ~buf_size (req:_ S.Request.t) (resp:S.Response.t) : _ option =
if accept_deflate req then ( if accept_deflate req then (
let set_headers h = let set_headers h =
h h
|> S.Headers.remove "Content-Length" |> S.Headers.remove "Content-Length"
|> S.Headers.set "Content-Encoding" "deflate" |> S.Headers.set "Content-Encoding" "deflate, chunked"
in in
match resp.body with match resp.body with
| `String s when String.length s > 500 * 1024 -> | `String s when String.length s > compress_above ->
S._debug (fun k->k "encode str response with deflate"); S._debug
(fun k->k "encode str response with deflate (size %d, threshold %d)"
(String.length s) compress_above);
let body = let body =
encode_deflate_stream_ ~buf_size @@ S.Byte_stream.of_string s encode_deflate_stream_ ~buf_size @@ S.Byte_stream.of_string s
in in
Some { Some {
resp with resp with
headers= set_headers resp.headers; body=`Stream body; headers=set_headers resp.headers; body=`Stream body;
} }
| `Stream str -> | `Stream str ->
S._debug (fun k->k "encode stream response with deflate"); S._debug (fun k->k "encode stream response with deflate");
@ -168,10 +192,12 @@ let cb_encode_compressed_stream
| `String _ -> None | `String _ -> None
) else None ) else None
let setup ?(buf_size=48 * 1_024) (server:S.t) : unit = let setup
?(compress_above=500*1024)
?(buf_size=48 * 1_024) (server:S.t) : unit =
let buf_size = max buf_size 1_024 in let buf_size = max buf_size 1_024 in
S._debug (fun k->k "setup gzip support (buf-size %d)" buf_size); S._debug (fun k->k "setup gzip support (buf-size %d)" buf_size);
S.add_decode_request_cb server (cb_decode_compressed_stream ~buf_size); S.add_decode_request_cb server (cb_decode_compressed_stream ~buf_size);
S.add_encode_response_cb server (cb_encode_compressed_stream ~buf_size); S.add_encode_response_cb server (cb_encode_compressed_stream ~compress_above ~buf_size);
() ()

View file

@ -1,5 +1,8 @@
val setup : ?buf_size:int -> Tiny_httpd.t -> unit val setup :
?compress_above:int ->
?buf_size:int -> Tiny_httpd.t -> unit
(** Install callbacks for tiny_httpd to be able to encode/decode (** Install callbacks for tiny_httpd to be able to encode/decode
compressed streams compressed streams
@param compress_above threshold above with string responses are compressed
@param buf_size size of the underlying buffer for compression/decompression *) @param buf_size size of the underlying buffer for compression/decompression *)