Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F113446
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 25, 6:01 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39563
Default Alt Text
(13 KB)
Attached To
Mode
R28 tesla
Attached
Detach File
Event Timeline
Log In to Comment