diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ec3bf2..c7c5f22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,8 +25,8 @@ jobs: otp: "25.3.2.21" benchmark: false - pair: - elixir: "1.20.1" - otp: "29.0.2" + elixir: "1.20.2" + otp: "29.0.4" benchmark: true env: MIX_ENV: test diff --git a/CHANGELOG.md b/CHANGELOG.md index de60511..5d51479 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.11.0] - 2026-07-28 + +### Changed + +- support `req` 0.7.x after changes that landed in https://github.com/wojtekmach/req/pull/550 changed internals + ## [0.10.1] - 2026-07-01 ### Changed diff --git a/README.md b/README.md index bd32941..dc1c6b4 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The package can be installed by adding `http_cookie` to your list of dependencie ```elixir def deps do [ - {:http_cookie, "~> 0.10.1"} + {:http_cookie, "~> 0.11.0"} ] end ``` diff --git a/lib/http_cookie/req_plugin.ex b/lib/http_cookie/req_plugin.ex index 3370cc7..e31f8d6 100644 --- a/lib/http_cookie/req_plugin.ex +++ b/lib/http_cookie/req_plugin.ex @@ -21,20 +21,7 @@ if Code.ensure_loaded?(Req) do end defp add_cookies(%{options: %{cookie_jar: cookie_jar}} = request) when cookie_jar != nil do - {request, original_cookie_header} = - if Req.Request.get_private(request, :req_redirect_count, 0) == 0 do - original_header = - request - |> Req.Request.get_header("cookie") - |> List.first() - - request = - Req.Request.put_private(request, :http_cookie_orig_cookie_header, original_header) - - {request, original_header} - else - {request, Req.Request.get_private(request, :http_cookie_orig_cookie_header)} - end + {request, original_cookie_header} = original_cookie_header(request) case HttpCookie.Jar.get_cookie_header_value(cookie_jar, request.url) do {:ok, value, updated_jar} -> @@ -54,16 +41,29 @@ if Code.ensure_loaded?(Req) do defp add_cookies(request), do: request - defp update_cookies({%{options: %{cookie_jar: cookie_jar}} = request, response}) when cookie_jar != nil do - # req doesn't run request steps after a redirect again, but we need that to include any cookies - # that might have been returned in the redirect response for the next request - # - # Wojtek suggested this as a workaround until there is a better solution - request = %{ - request - | current_request_steps: request.current_request_steps ++ [:add_cookies] - } + # Capture the user's original "cookie" header exactly once, on the first run of this + # step. Req re-runs request steps on every redirect hop and retry attempt, so we stash + # the original value in the request's private store and reuse it on later runs. This + # ensures we only avoid overriding a header the *user* set, not one we set ourselves. + defp original_cookie_header(request) do + case Req.Request.get_private(request, :http_cookie_orig_cookie_header, :unset) do + :unset -> + original_header = + request + |> Req.Request.get_header("cookie") + |> List.first() + + request = + Req.Request.put_private(request, :http_cookie_orig_cookie_header, original_header) + + {request, original_header} + + original_header -> + {request, original_header} + end + end + defp update_cookies({%{options: %{cookie_jar: cookie_jar}} = request, response}) when cookie_jar != nil do headers = Enum.flat_map(response.headers, fn {name, vals} -> Enum.map(vals, &{name, &1}) diff --git a/mise.toml b/mise.toml index fb756e4..c38a53d 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,3 @@ [tools] -elixir = "1.20.1-otp-29" -erlang = "29.0.2" +elixir = "1.20.2-otp-29" +erlang = "29.0.4" diff --git a/mix.exs b/mix.exs index 4dc3142..38b83e3 100644 --- a/mix.exs +++ b/mix.exs @@ -1,7 +1,7 @@ defmodule HttpCookie.MixProject do use Mix.Project - @version "0.10.1" + @version "0.11.0" @source_url "https://github.com/reisub/http_cookie" def project do @@ -56,7 +56,7 @@ defmodule HttpCookie.MixProject do {:ex_doc, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:jump_credo_checks, "~> 0.2", only: [:dev], runtime: false}, {:quokka, "~> 2.12", only: [:dev, :test], runtime: false}, - {:req, "~> 0.5.0 or ~> 0.6.0", optional: true}, + {:req, "~> 0.7.1", optional: true}, {:tesla, "~> 1.11", optional: true}, {:plug, "~> 1.0", only: :test}, {:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 4896cff..9a73e37 100644 --- a/mix.lock +++ b/mix.lock @@ -9,10 +9,10 @@ "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "erlex": {:hex, :erlex, "0.2.9", "7debbbaa9f4f368b8cd648983e0f1d7963028508e9c59e9d4ed504e94ef52a55", [:mix], [], "hexpm", "8cfffc0ec7159e6d73de2ab28a588064de80f88b2798d5cbe4482cbbc200178b"}, "ex_doc": {:hex, :ex_doc, "0.40.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"}, - "ex_slop": {:hex, :ex_slop, "0.4.2", "142aba9a82eddfb258e39c45d59392ab3cdb6b5a3ad401b09b362b7134fc54eb", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "c7f5316f755f83566e7a0a049f6fedfcd5ff916fce83c6ebfdf806be62fd7a69"}, + "ex_slop": {:hex, :ex_slop, "0.4.4", "8ca277b902e93193eb3545e414c309026a51204e5eb7d6ada5ff96555de6abcb", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "03ef1b2553ebad45c8ceb3e482eed5f1ccd08fc992215a9da0ef6196935107e2"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"}, - "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, "idna": {:hex, :idna, "7.1.0", "1067a13043538129602d2f2ce6899d8713125c7d19734aa557ce2e3ea55bd4f1", [:rebar3], [], "hexpm", "6ae959a025bf36df61a8cab8508d9654891b5426a84c44d82deaffd6ddf8c71f"}, "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, "jump_credo_checks": {:hex, :jump_credo_checks, "0.4.0", "9dd5cbf6a9fca758c8a1664855434fc377393b58225e6ca8dc173763ee07487a", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}, {:igniter, ">= 0.0.0", [hex: :igniter, repo: "hexpm", optional: true]}], "hexpm", "89f51e654b5f4900dfcc8cfaae780d676bc9343ec072f6067594f0a5c2900a19"}, @@ -20,15 +20,15 @@ "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.1.0", "835f7e60792e08824cda445639555d7bf1bbbddb1b60b306e33cb6f6db24dc74", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "1cd6780fb1dd1a03979abaed0fe82712b0625118fd5257d3ebbf73f960c73c3c"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, - "mint": {:hex, :mint, "1.9.0", "d6f534c2a3e98b2a8cc749b4796eb77e9e3af79a76f96e4c74035a827de0d318", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "007154c7d8c43916aed3c93afd1f11aebbaa9c5ff4b7ba55ebe0d17ee0296042"}, + "mint": {:hex, :mint, "1.9.3", "3337184d69179695c7a9f1714d92c11e629d36c8c037a21cf490131d3d150554", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "5f7c9342480c069dbbc4eeac3490303c9e01870ff01a7f1d29b6107054fc1e74"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "plug": {:hex, :plug, "1.20.2", "adbee2441232412e37fbb357fd5e4cd533fdd253b29f2e1992262b0f1fb01462", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b16baf55877d60891002ffc1ce0b3ff7d6f30a38a23e02e4d4293c4ac266f136"}, - "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, - "public_sufx": {:hex, :public_sufx, "0.7.20260528", "28fbb95b03ee01d855580eb3dedc6833398dba5ea04dd75b0116ced55e747ecc", [:mix], [{:idna, "~> 7.1", [hex: :idna, repo: "hexpm", optional: false]}], "hexpm", "21c8fea64d4df2ecbc144f52e343963e63387744b7d689eb83be94defb195b5c"}, + "plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"}, + "plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"}, + "public_sufx": {:hex, :public_sufx, "0.7.20260709", "091fdc5e36d31cec5e5cf18516a9a0276c41fe94c6c70f8adfdc8c4264e0d1fa", [:mix], [{:idna, "~> 7.1", [hex: :idna, repo: "hexpm", optional: false]}], "hexpm", "222fe36c5b7f2f7b4cf2b8eddcee800f5cece4c0572cb87a0291704ac8fc51ab"}, "quokka": {:hex, :quokka, "2.13.1", "10851ba49ce47cb65cb74b252fd7bfb8f9f78d6a0b1908228f1dae4b65c32939", [:mix], [{:credo, "~> 1.7", [hex: :credo, repo: "hexpm", optional: false]}], "hexpm", "f6b3c60d333f76e5e131b9d4b55aed50e989589ee4cb3dc646176ceec26bf0f5"}, - "req": {:hex, :req, "0.6.2", "b9b2024f35bcf60a92cc8cad2eaaf9d4e7aace463ff74be1afe5986830184413", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cc9cd30a2ddd04989929b887178e1610c940456d962c6c3a52df6146d2eef9bf"}, + "req": {:hex, :req, "0.7.1", "86271f9e29dca83d382222428ced09ba03bad21e24888c50dd4527553d85f642", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "254638b15ceb9a2624d15aff13bf7903ea2e95cd4b9c1aa18da1fb06e1086b50"}, "statistex": {:hex, :statistex, "1.1.1", "73612aa7f79e53c30569be065fd121e380f1cf57bc4c2da5b41be9246da18df9", [:mix], [], "hexpm", "310c4b49b34adf683de3103639006bed233ab54c08a4add65a531448e653857c"}, "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "tesla": {:hex, :tesla, "1.20.0", "922b504e186da2ee82990d44e7422435f41b67403908a00340c3a0b23a17aa90", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21 or >= 4.0.2 and < 5.0.0-0", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:opentelemetry_semantic_conventions, "~> 1.27", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3ecb41cb458772332752c3acdfe983e23abb991f5a43cfd69a64e9ea3f4b0061"}, diff --git a/test/http_cookie/req_test.exs b/test/http_cookie/req_test.exs index f7f2aec..3cebe4a 100644 --- a/test/http_cookie/req_test.exs +++ b/test/http_cookie/req_test.exs @@ -95,6 +95,44 @@ defmodule HttpCookie.ReqTest do assert %{private: %{cookie_jar: _updated_jar}} = Req.get!(req, url: "/redirect-me") end + test "re-applies cookies from the jar on retry" do + {:ok, counter} = Agent.start_link(fn -> 0 end) + + plug = fn %{request_path: "/retry"} = conn -> + attempt = Agent.get_and_update(counter, fn n -> {n, n + 1} end) + conn = Plug.Conn.fetch_cookies(conn) + + case attempt do + 0 -> + conn + |> Plug.Conn.put_resp_header("set-cookie", "gained=two") + |> Plug.Conn.resp(500, "try again") + + _ -> + assert conn.req_cookies == %{"existing" => "one", "gained" => "two"} + Plug.Conn.resp(conn, 200, "you made it") + end + end + + jar = + HttpCookie.Jar.new() + |> HttpCookie.Jar.put_cookies_from_headers( + URI.parse("https://example.com/"), + [{"set-cookie", "existing=one"}] + ) + + req = + Req.new( + base_url: "https://example.com", + plug: plug, + retry_delay: 1, + retry_log_level: false + ) + |> ReqPlugin.attach(cookie_jar: jar) + + assert %{status: 200} = Req.get!(req, url: "/retry") + end + test "doesn't override existing cookie header" do plug = fn