Skip to content

Commit 8df27ac

Browse files
authored
Add support for Plug.RewriteOn to rewrite nonstandard headers (#1272)
1 parent 57d04d7 commit 8df27ac

File tree

2 files changed

+111
-9
lines changed

2 files changed

+111
-9
lines changed

lib/plug/rewrite_on.ex

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@ defmodule Plug.RewriteOn do
1010
1111
The supported values are:
1212
13-
* `:x_forwarded_for` - to override the remote ip based on the "x-forwarded-for" header
13+
* `:x_forwarded_for` - to override the remote IP based on the "x-forwarded-for" header
1414
* `:x_forwarded_host` - to override the host based on the "x-forwarded-host" header
1515
* `:x_forwarded_port` - to override the port based on the "x-forwarded-port" header
1616
* `:x_forwarded_proto` - to override the protocol based on the "x-forwarded-proto" header
1717
18+
Some HTTPS proxies use nonstandard headers, which can be specified in the list via tuples:
19+
20+
* `{:remote_ip, header}` - to override the remote IP based on a custom header
21+
* `{:host, header}` - to override the host based on a custom header
22+
* `{:port, header}` - to override the port based on a custom header
23+
* `{:scheme, header}` - to override the protocol based on a custom header
24+
1825
A tuple representing a Module-Function-Args can also be given as argument
1926
instead of a list.
2027
@@ -30,31 +37,47 @@ defmodule Plug.RewriteOn do
3037
import Plug.Conn, only: [get_req_header: 2]
3138

3239
@impl true
33-
def init(header) when is_tuple(header), do: header
40+
def init({_m, _f, _a} = header), do: header
3441
def init(header), do: List.wrap(header)
3542

3643
@impl true
3744
def call(conn, [:x_forwarded_for | rewrite_on]) do
45+
call(conn, [{:remote_ip, "x-forwarded-for"} | rewrite_on])
46+
end
47+
48+
def call(conn, [:x_forwarded_proto | rewrite_on]) do
49+
call(conn, [{:scheme, "x-forwarded-proto"} | rewrite_on])
50+
end
51+
52+
def call(conn, [:x_forwarded_port | rewrite_on]) do
53+
call(conn, [{:port, "x-forwarded-port"} | rewrite_on])
54+
end
55+
56+
def call(conn, [:x_forwarded_host | rewrite_on]) do
57+
call(conn, [{:host, "x-forwarded-host"} | rewrite_on])
58+
end
59+
60+
def call(conn, [{:remote_ip, header} | rewrite_on]) do
3861
conn
39-
|> put_remote_ip(get_req_header(conn, "x-forwarded-for"))
62+
|> put_remote_ip(get_req_header(conn, header))
4063
|> call(rewrite_on)
4164
end
4265

43-
def call(conn, [:x_forwarded_proto | rewrite_on]) do
66+
def call(conn, [{:scheme, header} | rewrite_on]) do
4467
conn
45-
|> put_scheme(get_req_header(conn, "x-forwarded-proto"))
68+
|> put_scheme(get_req_header(conn, header))
4669
|> call(rewrite_on)
4770
end
4871

49-
def call(conn, [:x_forwarded_port | rewrite_on]) do
72+
def call(conn, [{:port, header} | rewrite_on]) do
5073
conn
51-
|> put_port(get_req_header(conn, "x-forwarded-port"))
74+
|> put_port(get_req_header(conn, header))
5275
|> call(rewrite_on)
5376
end
5477

55-
def call(conn, [:x_forwarded_host | rewrite_on]) do
78+
def call(conn, [{:host, header} | rewrite_on]) do
5679
conn
57-
|> put_host(get_req_header(conn, "x-forwarded-host"))
80+
|> put_host(get_req_header(conn, header))
5881
|> call(rewrite_on)
5982
end
6083

test/plug/rewrite_on_test.exs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ defmodule Plug.RewriteOnTest do
2727
assert conn.port == 443
2828
end
2929

30+
test "rewrites http to https based on a custom header" do
31+
conn =
32+
conn(:get, "http://example.com/")
33+
|> put_req_header("custom-forwarded-proto", "https")
34+
|> call({:scheme, "custom-forwarded-proto"})
35+
36+
assert conn.scheme == :https
37+
assert conn.port == 443
38+
end
39+
3040
test "doesn't change the port when it doesn't match the scheme" do
3141
conn =
3242
conn(:get, "http://example.com:1234/")
@@ -46,6 +56,15 @@ defmodule Plug.RewriteOnTest do
4656
assert conn.host == "truessl.example.com"
4757
end
4858

59+
test "rewrites host with a custom header" do
60+
conn =
61+
conn(:get, "http://example.com/")
62+
|> put_req_header("custom-forwarded-host", "truessl.example.com")
63+
|> call({:host, "custom-forwarded-host"})
64+
65+
assert conn.host == "truessl.example.com"
66+
end
67+
4968
test "rewrites port with a x-forwarded-port header" do
5069
conn =
5170
conn(:get, "http://example.com/")
@@ -55,6 +74,15 @@ defmodule Plug.RewriteOnTest do
5574
assert conn.port == 3030
5675
end
5776

77+
test "rewrites port with a custom header" do
78+
conn =
79+
conn(:get, "http://example.com/")
80+
|> put_req_header("custom-forwarded-port", "3030")
81+
|> call({:port, "custom-forwarded-port"})
82+
83+
assert conn.port == 3030
84+
end
85+
5886
test "rewrites remote_ip with a x-forwarded-for header" do
5987
conn =
6088
conn(:get, "http://example.com/")
@@ -85,6 +113,38 @@ defmodule Plug.RewriteOnTest do
85113
assert conn.remote_ip == {0, 0, 0, 0, 0, 0, 0, 1}
86114
end
87115

116+
117+
test "rewrites remote_ip with a custom header" do
118+
conn =
119+
conn(:get, "http://example.com/")
120+
|> put_req_header("custom-forwarded-for", "bad")
121+
|> call({:remote_ip, "custom-forwarded-for"})
122+
123+
assert conn.remote_ip == {127, 0, 0, 1}
124+
125+
conn =
126+
conn(:get, "http://example.com/")
127+
|> put_req_header("custom-forwarded-for", "4.3.2.1")
128+
|> call({:remote_ip, "custom-forwarded-for"})
129+
130+
assert conn.remote_ip == {4, 3, 2, 1}
131+
132+
conn =
133+
conn(:get, "http://example.com/")
134+
|> put_req_header("custom-forwarded-for", "1.2.3.4,::1")
135+
|> call({:remote_ip, "custom-forwarded-for"})
136+
137+
assert conn.remote_ip == {1, 2, 3, 4}
138+
139+
conn =
140+
conn(:get, "http://example.com/")
141+
|> put_req_header("custom-forwarded-for", "::1,1.2.3.4")
142+
|> call({:remote_ip, "custom-forwarded-for"})
143+
144+
assert conn.remote_ip == {0, 0, 0, 0, 0, 0, 0, 1}
145+
end
146+
147+
88148
test "rewrites the host, the port, and the protocol" do
89149
conn =
90150
conn(:get, "http://example.com/")
@@ -98,9 +158,28 @@ defmodule Plug.RewriteOnTest do
98158
assert conn.scheme == :https
99159
end
100160

161+
162+
test "rewrites the host, the port, and the protocol with custom headers" do
163+
conn =
164+
conn(:get, "http://example.com/")
165+
|> put_req_header("custom-forwarded-host", "truessl.example.com")
166+
|> put_req_header("custom-forwarded-port", "3030")
167+
|> put_req_header("custom-forwarded-proto", "https")
168+
|> call([host: "custom-forwarded-host",
169+
port: "custom-forwarded-port",
170+
scheme: "custom-forwarded-proto"])
171+
172+
assert conn.host == "truessl.example.com"
173+
assert conn.port == 3030
174+
assert conn.scheme == :https
175+
end
176+
101177
test "raises when receiving an unknown rewrite" do
102178
assert_raise RuntimeError, "unknown rewrite: :x_forwarded_other", fn ->
103179
call(conn(:get, "http://example.com/"), :x_forwarded_other)
104180
end
181+
assert_raise RuntimeError, "unknown rewrite: {:other, \"value\"}", fn ->
182+
call(conn(:get, "http://example.com/"), [other: "value"])
183+
end
105184
end
106185
end

0 commit comments

Comments
 (0)