From 778d58a7cec0071566b01d62ed2fe495dc4cc174 Mon Sep 17 00:00:00 2001 From: Wojtek Mach Date: Mon, 19 Jun 2023 19:40:03 +0200 Subject: [PATCH] `decompress_body`: Support multiple `content-encoding` headers Closes #191 --- lib/req/steps.ex | 23 ++++------- test/req/steps_test.exs | 87 +++++++++++++++++++++++++++++------------ 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/lib/req/steps.ex b/lib/req/steps.ex index 7fec24e3..2fe59df0 100644 --- a/lib/req/steps.ex +++ b/lib/req/steps.ex @@ -1487,25 +1487,18 @@ defmodule Req.Steps do ## Utilities defp get_content_encoding_header(headers) do - if value = get_header(headers, "content-encoding") do - value - |> String.downcase() - |> String.split(",", trim: true) - |> Stream.map(&String.trim/1) - |> Enum.reverse() - else - [] - end - end - - defp get_header(headers, name) do - Enum.find_value(headers, nil, fn {key, value} -> - if String.downcase(key) == name do + headers + |> Enum.flat_map(fn {name, value} -> + if String.downcase(name) == "content-encoding" do value + |> String.downcase() + |> String.split(",", trim: true) + |> Stream.map(&String.trim/1) else - nil + [] end end) + |> Enum.reverse() end defp cache_path(cache_dir, request) do diff --git a/test/req/steps_test.exs b/test/req/steps_test.exs index 50fe4a4c..8468db31 100644 --- a/test/req/steps_test.exs +++ b/test/req/steps_test.exs @@ -246,37 +246,74 @@ defmodule Req.StepsTest do ## Response steps - test "decompress_body: gzip", c do - Bypass.expect(c.bypass, "GET", "/", fn conn -> - conn - |> Plug.Conn.put_resp_header("content-encoding", "x-gzip") - |> Plug.Conn.send_resp(200, :zlib.gzip("foo")) - end) + describe "decompress_body" do + test "gzip", c do + Bypass.expect(c.bypass, "GET", "/", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-encoding", "x-gzip") + |> Plug.Conn.send_resp(200, :zlib.gzip("foo")) + end) - assert Req.get!(c.url).body == "foo" - end + assert Req.get!(c.url).body == "foo" + end - test "decompress_body: brotli", c do - Bypass.expect(c.bypass, "GET", "/", fn conn -> - {:ok, body} = :brotli.encode("foo") + test "multiple codecs", c do + Bypass.expect(c.bypass, "GET", "/", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-encoding", "gzip, deflate") + |> Plug.Conn.send_resp(200, "foo" |> :zlib.gzip() |> :zlib.zip()) + end) - conn - |> Plug.Conn.put_resp_header("content-encoding", "br") - |> Plug.Conn.send_resp(200, body) - end) + assert Req.get!(c.url).body == "foo" + end - assert Req.get!(c.url).body == "foo" - end + test "multiple codecs with multiple headers" do + {:ok, listen_socket} = :gen_tcp.listen(0, mode: :binary, active: false) + {:ok, port} = :inet.port(listen_socket) - @tag :capture_log - test "decompress_body: zstd", c do - Bypass.expect(c.bypass, "GET", "/", fn conn -> - conn - |> Plug.Conn.put_resp_header("content-encoding", "zstd") - |> Plug.Conn.send_resp(200, :ezstd.compress("foo")) - end) + Task.start_link(fn -> + {:ok, socket} = :gen_tcp.accept(listen_socket) + assert {:ok, "GET / HTTP/1.1\r\n" <> _} = :gen_tcp.recv(socket, 0) - assert Req.get!(c.url).body == "foo" + body = "foo" |> :zlib.gzip() |> :zlib.zip() + + data = """ + HTTP/1.1 200 OK + content-encoding: gzip + content-encoding: deflate + content-length: #{byte_size(body)} + + #{body} + """ + + :ok = :gen_tcp.send(socket, data) + end) + + assert Req.get!("http://localhost:#{port}").body == "foo" + end + + test "brotli", c do + Bypass.expect(c.bypass, "GET", "/", fn conn -> + {:ok, body} = :brotli.encode("foo") + + conn + |> Plug.Conn.put_resp_header("content-encoding", "br") + |> Plug.Conn.send_resp(200, body) + end) + + assert Req.get!(c.url).body == "foo" + end + + @tag :capture_log + test "zstd", c do + Bypass.expect(c.bypass, "GET", "/", fn conn -> + conn + |> Plug.Conn.put_resp_header("content-encoding", "zstd") + |> Plug.Conn.send_resp(200, :ezstd.compress("foo")) + end) + + assert Req.get!(c.url).body == "foo" + end end @tag :tmp_dir