Page MenuHomePhorge

No OneTemporary

Size
13 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/middleware/follow_redirects.ex b/lib/tesla/middleware/follow_redirects.ex
index 923943e..c5ca76a 100644
--- a/lib/tesla/middleware/follow_redirects.ex
+++ b/lib/tesla/middleware/follow_redirects.ex
@@ -1,99 +1,95 @@
defmodule Tesla.Middleware.FollowRedirects do
@moduledoc """
Follow 3xx redirects
## Example
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.FollowRedirects, max_redirects: 3 # defaults to 5
end
```
## Options
- `:max_redirects` - limit number of redirects (default: `5`)
"""
@behaviour Tesla.Middleware
@max_redirects 5
@redirect_statuses [301, 302, 303, 307, 308]
@impl Tesla.Middleware
def call(env, next, opts \\ []) do
max = Keyword.get(opts || [], :max_redirects, @max_redirects)
redirect(env, next, max)
end
defp redirect(env, next, left) when left == 0 do
case Tesla.run(env, next) do
{:ok, %{status: status} = env} when not (status in @redirect_statuses) ->
{:ok, env}
{:ok, _env} ->
{:error, {__MODULE__, :too_many_redirects}}
error ->
error
end
end
defp redirect(env, next, left) do
case Tesla.run(env, next) do
{:ok, %{status: status} = res} when status in @redirect_statuses ->
case Tesla.get_header(res, "location") do
nil ->
{:ok, res}
location ->
- location = parse_location(location, res)
+ prev_uri = URI.parse(env.url)
+ next_uri = parse_location(location, res)
env
- |> new_request(res.status, location)
- |> filter_headers(env.url)
+ |> filter_headers(prev_uri, next_uri)
+ |> new_request(status, URI.to_string(next_uri))
|> redirect(next, left - 1)
end
other ->
other
end
end
# The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally
# requested resource is not available, however a related resource (or another redirect)
# available via GET is available at the specified location.
# https://tools.ietf.org/html/rfc7231#section-6.4.4
defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []}
# The 307 (Temporary Redirect) status code indicates that the target
# resource resides temporarily under a different URI and the user agent
# MUST NOT change the request method (...)
# https://tools.ietf.org/html/rfc7231#section-6.4.7
defp new_request(env, 307, location), do: %{env | url: location}
defp new_request(env, _, location), do: %{env | url: location, query: []}
- defp parse_location("https://" <> _rest = location, _env), do: location
- defp parse_location("http://" <> _rest = location, _env), do: location
-
- defp parse_location(location, env) do
- env.url
- |> URI.parse()
- |> URI.merge(location)
- |> URI.to_string()
- end
+ defp parse_location("https://" <> _rest = location, _env), do: URI.parse(location)
+ defp parse_location("http://" <> _rest = location, _env), do: URI.parse(location)
+ defp parse_location(location, env), do: env.url |> URI.parse() |> URI.merge(location)
# See https://github.com/teamon/tesla/issues/362
- @filter_headers ["authorization"]
- defp filter_headers(env, orig_url) do
- if URI.parse(env.url).host != URI.parse(orig_url).host do
+ # See https://github.com/teamon/tesla/issues/360
+ @filter_headers ["authorization", "host"]
+ defp filter_headers(env, prev, next) do
+ if next.host != prev.host || next.port != prev.port || next.scheme != prev.scheme do
%{env | headers: Enum.filter(env.headers, fn {k, _} -> k not in @filter_headers end)}
else
env
end
end
end
diff --git a/test/tesla/middleware/follow_redirects_test.exs b/test/tesla/middleware/follow_redirects_test.exs
index d8ec8de..edfbe6c 100644
--- a/test/tesla/middleware/follow_redirects_test.exs
+++ b/test/tesla/middleware/follow_redirects_test.exs
@@ -1,265 +1,301 @@
defmodule Tesla.Middleware.FollowRedirectsTest do
use ExUnit.Case
defmodule Client do
use Tesla
plug Tesla.Middleware.FollowRedirects
adapter fn env ->
{status, headers, body} =
case env.url do
"http://example.com/0" ->
assert env.query == []
{200, [{"content-type", "text/plain"}], "foo bar"}
"http://example.com/" <> n ->
next = String.to_integer(n) - 1
{301, [{"location", "http://example.com/#{next}"}], ""}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
test "redirects if default max redirects isn't exceeded" do
assert {:ok, env} = Client.get("http://example.com/5")
assert env.status == 200
end
test "raise error when redirect default max redirects is exceeded" do
assert {:error, {Tesla.Middleware.FollowRedirects, :too_many_redirects}} ==
Client.get("http://example.com/6")
end
test "drop the query" do
assert {:ok, env} = Client.get("http://example.com/5", some_query: "params")
assert env.query == []
end
defmodule CustomMaxRedirectsClient do
use Tesla
plug Tesla.Middleware.FollowRedirects, max_redirects: 1
adapter fn env ->
{status, headers, body} =
case env.url do
"http://example.com/0" ->
assert env.query == []
{200, [{"content-type", "text/plain"}], "foo bar"}
"http://example.com/" <> n ->
next = String.to_integer(n) - 1
{301, [{"location", "http://example.com/#{next}"}], ""}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
alias CustomMaxRedirectsClient, as: CMRClient
test "redirects if custom max redirects isn't exceeded" do
assert {:ok, env} = CMRClient.get("http://example.com/1")
assert env.status == 200
end
test "raise error when custom max redirects is exceeded" do
assert {:error, {Tesla.Middleware.FollowRedirects, :too_many_redirects}} ==
CMRClient.get("http://example.com/2")
end
defmodule RelativeLocationClient do
use Tesla
plug Tesla.Middleware.FollowRedirects
adapter fn env ->
{status, headers, body} =
case env.url do
"https://example.com/pl" ->
{200, [{"content-type", "text/plain"}], "foo bar"}
"http://example.com" ->
{301, [{"location", "https://example.com"}], ""}
"https://example.com" ->
{301, [{"location", "/pl"}], ""}
"https://example.com/" ->
{301, [{"location", "/pl"}], ""}
"https://example.com/article" ->
{301, [{"location", "/pl"}], ""}
"https://example.com/one/two" ->
{301, [{"location", "three"}], ""}
"https://example.com/one/three" ->
{200, [{"content-type", "text/plain"}], "foo bar baz"}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
alias RelativeLocationClient, as: RLClient
test "supports relative address in location header" do
assert {:ok, env} = RLClient.get("http://example.com")
assert env.status == 200
end
test "doesn't create double slashes inside new url" do
assert {:ok, env} = RLClient.get("https://example.com/")
assert env.url == "https://example.com/pl"
end
test "rewrites URLs to their root" do
assert {:ok, env} = RLClient.get("https://example.com/article")
assert env.url == "https://example.com/pl"
end
test "rewrites URLs relative to the original URL" do
assert {:ok, env} = RLClient.get("https://example.com/one/two")
assert env.url == "https://example.com/one/three"
end
defmodule CustomRewriteMethodClient do
use Tesla
plug Tesla.Middleware.FollowRedirects
adapter fn env ->
{status, headers, body} =
case env.url do
"http://example.com/0" ->
{200, [{"content-type", "text/plain"}], "foo bar"}
"http://example.com/" <> n ->
next = String.to_integer(n) - 1
{303, [{"location", "http://example.com/#{next}"}], ""}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
alias CustomRewriteMethodClient, as: CRMClient
test "rewrites method to get for 303 requests" do
assert {:ok, env} = CRMClient.post("http://example.com/1", "")
assert env.method == :get
end
defmodule CustomPreservesRequestClient do
use Tesla
plug Tesla.Middleware.FollowRedirects
adapter fn env ->
{status, headers, body} =
case env.url do
"http://example.com/0" ->
{200, env.headers, env.body}
"http://example.com/" <> n ->
next = String.to_integer(n) - 1
{307, [{"location", "http://example.com/#{next}"}], ""}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
alias CustomPreservesRequestClient, as: CPRClient
test "Preserves original request for 307" do
assert {:ok, env} =
CPRClient.post(
"http://example.com/1",
"Body data",
headers: [{"X-Custom-Header", "custom value"}]
)
assert env.method == :post
assert env.body == "Body data"
assert env.headers == [{"X-Custom-Header", "custom value"}]
end
- describe "authorization headers" do
+ describe "headers" do
defp setup_client(_) do
middleware = [Tesla.Middleware.FollowRedirects]
adapter = fn
%{url: "http://example.com/keep", headers: headers} = env ->
send(self(), headers)
{:ok,
%{env | status: 301, headers: [{"location", "http://example.com/next"}], body: ""}}
%{url: "http://example.com/drop", headers: headers} = env ->
send(self(), headers)
{:ok,
%{env | status: 301, headers: [{"location", "http://example.net/next"}], body: ""}}
%{url: "http://example.com/next", headers: headers} = env ->
send(self(), headers)
{:ok, %{env | status: 200, headers: [], body: "ok com"}}
%{url: "http://example.net/next", headers: headers} = env ->
send(self(), headers)
{:ok, %{env | status: 200, headers: [], body: "ok net"}}
end
{:ok, client: Tesla.client(middleware, adapter)}
end
setup :setup_client
test "Keep authorization header on redirect to the same domain", %{client: client} do
assert {:ok, env} =
Tesla.post(client, "http://example.com/keep", "",
headers: [
{"content-type", "text/plain"},
- {"authorization", "Basic: secret"}
+ {"authorization", "Basic Zm9vOmJhcg=="}
]
)
# Initial request receives all headers
assert_receive [
{"content-type", "text/plain"},
- {"authorization", "Basic: secret"}
+ {"authorization", "Basic Zm9vOmJhcg=="}
]
# Next request also receives all headers
assert_receive [
{"content-type", "text/plain"},
- {"authorization", "Basic: secret"}
+ {"authorization", "Basic Zm9vOmJhcg=="}
]
end
test "Strip authorization header on redirect to a different domain", %{client: client} do
assert {:ok, env} =
Tesla.post(client, "http://example.com/drop", "",
headers: [
{"content-type", "text/plain"},
- {"authorization", "Basic: secret"}
+ {"authorization", "Basic Zm9vOmJhcg=="}
]
)
# Initial request receives all headers
assert_receive [
{"content-type", "text/plain"},
- {"authorization", "Basic: secret"}
+ {"authorization", "Basic Zm9vOmJhcg=="}
]
# Next request does not receive authorization header
assert_receive [
{"content-type", "text/plain"}
]
end
+
+ test "Keep custom host header on redirect to a different domain", %{client: client} do
+ assert {:ok, env} =
+ Tesla.post(client, "http://example.com/keep", "",
+ headers: [
+ {"host", "example.xyz"}
+ ]
+ )
+
+ # Initial request receives host header
+ assert_receive [
+ {"host", "example.xyz"}
+ ]
+
+ # Next request does not receive host header
+ assert_receive [
+ {"host", "example.xyz"}
+ ]
+ end
+
+ test "Strip custom host header on redirect to a different domain", %{client: client} do
+ assert {:ok, env} =
+ Tesla.post(client, "http://example.com/drop", "",
+ headers: [
+ {"host", "example.xyz"}
+ ]
+ )
+
+ # Initial request receives host header
+ assert_receive [
+ {"host", "example.xyz"}
+ ]
+
+ # Next request does not receive host header
+ assert_receive []
+ end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 3:46 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39563
Default Alt Text
(13 KB)

Event Timeline