changed CHANGELOG.md
 
@@ -1,5 +1,20 @@
1
1
# Changelog
2
2
3
+ ## v1.16.0 (2024-05-18)
4
+
5
+ ### Enhancements
6
+
7
+ * Support x-forwarded-for in Plug.RewriteOn
8
+ * Support MFArgs in Plug.RewriteOn
9
+ * Add immutable directive to versioned requests in `Plug.Static`
10
+ * Support disabling MIME type handling in `Plug.Static`
11
+
12
+ ### Bug fixes
13
+
14
+ * Fix bug with discarded connection state in `Plug.Debugger`
15
+ * Parse media types with underscores in them
16
+ * Do not crash on `max_age` set to nil (for consistency)
17
+
3
18
## v1.15.3 (2024-01-16)
4
19
5
20
### Enhancements
changed README.md
 
@@ -188,12 +188,13 @@ Now run `mix run --no-halt` and it will start your application with a web server
188
188
189
189
| Branch | Support |
190
190
|--------|--------------------------|
191
- | v1.14 | Bug fixes |
191
+ | v1.15 | Bug fixes |
192
+ | v1.14 | Security patches only |
192
193
| v1.13 | Security patches only |
193
194
| v1.12 | Security patches only |
194
195
| v1.11 | Security patches only |
195
196
| v1.10 | Security patches only |
196
- | v1.9 | Security patches only |
197
+ | v1.9 | Unsupported from 10/2023 |
197
198
| v1.8 | Unsupported from 01/2023 |
198
199
| v1.7 | Unsupported from 01/2022 |
199
200
| v1.6 | Unsupported from 01/2022 |
changed hex_metadata.config
 
@@ -1,6 +1,6 @@
1
1
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/elixir-plug/plug">>}]}.
2
2
{<<"name">>,<<"plug">>}.
3
- {<<"version">>,<<"1.15.3">>}.
3
+ {<<"version">>,<<"1.16.0">>}.
4
4
{<<"description">>,<<"Compose web applications with functions">>}.
5
5
{<<"elixir">>,<<"~> 1.10">>}.
6
6
{<<"app">>,<<"plug">>}.
changed lib/plug/basic_auth.ex
 
@@ -38,7 +38,7 @@ defmodule Plug.BasicAuth do
38
38
39
39
plug :auth
40
40
41
- defp auth(conn, opts) do
41
+ defp auth(conn, _opts) do
42
42
username = System.fetch_env!("AUTH_USERNAME")
43
43
password = System.fetch_env!("AUTH_PASSWORD")
44
44
Plug.BasicAuth.basic_auth(conn, username: username, password: password)
 
@@ -73,7 +73,8 @@ defmodule Plug.BasicAuth do
73
73
* The supplied `user` and `pass` may be empty strings;
74
74
75
75
* If you are comparing the username and password with existing strings,
76
- do not use `==/2`. Use `Plug.Crypto.secure_compare/2` instead.
76
+ do not use `==/2` or pattern matching. Use `Plug.Crypto.secure_compare/2`
77
+ instead.
77
78
78
79
"""
79
80
import Plug.Conn
changed lib/plug/conn.ex
 
@@ -533,7 +533,7 @@ defmodule Plug.Conn do
533
533
`send_chunked/2`. It returns `{:ok, conn}` in case of success,
534
534
otherwise `{:error, reason}`.
535
535
536
- To stream data use `Enum.reduce_while/3` instead of `Enum.into/2`.
536
+ To stream data use `Enum.reduce_while/3` instead of `Enum.reduce/2`.
537
537
`Enum.reduce_while/3` allows aborting the execution if `chunk/2` fails to
538
538
deliver the chunk of data.
539
539
 
@@ -1644,7 +1644,8 @@ defmodule Plug.Conn do
1644
1644
end
1645
1645
1646
1646
defp max_age(opts) do
1647
- [keys: Plug.Keys, max_age: Keyword.get(opts, :max_age, 86400)]
1647
+ max_age = Keyword.get(opts, :max_age) || 86400
1648
+ [keys: Plug.Keys, max_age: max_age]
1648
1649
end
1649
1650
1650
1651
defp maybe_secure_cookie(cookie, :https), do: Map.put_new(cookie, :secure, true)
changed lib/plug/conn/utils.ex
 
@@ -8,7 +8,7 @@ defmodule Plug.Conn.Utils do
8
8
@upper ?A..?Z
9
9
@lower ?a..?z
10
10
@alpha ?0..?9
11
- @other [?., ?-, ?+]
11
+ @other [?., ?-, ?+, ?_]
12
12
@space [?\s, ?\t]
13
13
@specials ~c|()<>@,;:\\"/[]?={}|
14
14
 
@@ -32,6 +32,9 @@ defmodule Plug.Conn.Utils do
32
32
iex> media_type "APPLICATION/vnd.ms-data+XML"
33
33
{:ok, "application", "vnd.ms-data+xml", %{}}
34
34
35
+ iex> media_type "application/media_control+xml"
36
+ {:ok, "application", "media_control+xml", %{}}
37
+
35
38
iex> media_type "text/*; q=1.0"
36
39
{:ok, "text", "*", %{"q" => "1.0"}}
changed lib/plug/debugger.ex
 
@@ -231,7 +231,7 @@ defmodule Plug.Debugger do
231
231
232
232
@doc false
233
233
def run_action(%Plug.Conn{} = conn) do
234
- with %Plug.Conn{body_params: params} <- fetch_body_params(conn),
234
+ with %Plug.Conn{body_params: params} = conn <- fetch_body_params(conn),
235
235
{:ok, {module, function, args}} <-
236
236
Plug.Crypto.verify(conn.secret_key_base, @salt, params["encoded_handler"]) do
237
237
apply(module, function, args)
changed lib/plug/rewrite_on.ex
 
@@ -10,10 +10,14 @@ defmodule Plug.RewriteOn do
10
10
11
11
The supported values are:
12
12
13
+ * `:x_forwarded_for` - to override the remote ip based on on the "x-forwarded-for" header
13
14
* `:x_forwarded_host` - to override the host based on on the "x-forwarded-host" header
14
15
* `:x_forwarded_port` - to override the port based on on the "x-forwarded-port" header
15
16
* `:x_forwarded_proto` - to override the protocol based on on the "x-forwarded-proto" header
16
17
18
+ A tuple representing a Module-Function-Args can also be given as argument
19
+ instead of a list.
20
+
17
21
Since rewriting the scheme based on `x-forwarded-*` headers can open up
18
22
security vulnerabilities, only use this plug if:
19
23
 
@@ -26,9 +30,16 @@ defmodule Plug.RewriteOn do
26
30
import Plug.Conn, only: [get_req_header: 2]
27
31
28
32
@impl true
33
+ def init(header) when is_tuple(header), do: header
29
34
def init(header), do: List.wrap(header)
30
35
31
36
@impl true
37
+ def call(conn, [:x_forwarded_for | rewrite_on]) do
38
+ conn
39
+ |> put_remote_ip(get_req_header(conn, "x-forwarded-for"))
40
+ |> call(rewrite_on)
41
+ end
42
+
32
43
def call(conn, [:x_forwarded_proto | rewrite_on]) do
33
44
conn
34
45
|> put_scheme(get_req_header(conn, "x-forwarded-proto"))
 
@@ -55,6 +66,10 @@ defmodule Plug.RewriteOn do
55
66
conn
56
67
end
57
68
69
+ def call(conn, {mod, fun, args}) do
70
+ call(conn, apply(mod, fun, args))
71
+ end
72
+
58
73
defp put_scheme(%{scheme: :http, port: 80} = conn, ["https"]),
59
74
do: %{conn | scheme: :https, port: 443}
60
75
 
@@ -84,4 +99,14 @@ defmodule Plug.RewriteOn do
84
99
_ -> conn
85
100
end
86
101
end
102
+
103
+ defp put_remote_ip(conn, headers) do
104
+ with [header] <- headers,
105
+ [client | _] <- :binary.split(header, ","),
106
+ {:ok, remote_ip} <- :inet.parse_address(String.to_charlist(client)) do
107
+ %{conn | remote_ip: remote_ip}
108
+ else
109
+ _ -> conn
110
+ end
111
+ end
87
112
end
changed lib/plug/static.ex
 
@@ -100,9 +100,13 @@ defmodule Plug.Static do
100
100
an enum of key-value pairs or a `{module, function, args}` to return an enum. The
101
101
`conn` will be passed to the function, as well as the `args`.
102
102
103
- * `:content_types` - custom MIME type mapping. As a map with filename as key
104
- and content type as value. For example:
103
+ * `:content_types` - controls custom MIME type mapping.
104
+ It can be a map with filename as key and content type as value to override
105
+ the default type for matching filenames. For example:
105
106
`content_types: %{"apple-app-site-association" => "application/json"}`.
107
+ Alternatively, it can be the value `false` to opt out of setting the header at all. The latter
108
+ can be used to set the header based on custom logic before calling this plug.
109
+ Defaults to an empty map `%{}`.
106
110
107
111
## Examples
108
112
 
@@ -163,7 +167,7 @@ defmodule Plug.Static do
163
167
%{
164
168
encodings: encodings,
165
169
only_rules: {Keyword.get(opts, :only, []), Keyword.get(opts, :only_matching, [])},
166
- qs_cache: Keyword.get(opts, :cache_control_for_vsn_requests, "public, max-age=31536000"),
170
+ qs_cache: Keyword.get(opts, :cache_control_for_vsn_requests, "public, max-age=31536000, immutable"),
167
171
et_cache: Keyword.get(opts, :cache_control_for_etags, "public"),
168
172
et_generation: Keyword.get(opts, :etag_generation, nil),
169
173
headers: Keyword.get(opts, :headers, %{}),
 
@@ -218,6 +222,13 @@ defmodule Plug.Static do
218
222
h in full or (prefix != [] and match?({0, _}, :binary.match(h, prefix)))
219
223
end
220
224
225
+ defp maybe_put_content_type(conn, false, _), do: conn
226
+
227
+ defp maybe_put_content_type(conn, types, filename) do
228
+ content_type = Map.get(types, filename) || MIME.from_path(filename)
229
+ put_resp_header(conn, "content-type", content_type)
230
+ end
231
+
221
232
defp serve_static({content_encoding, file_info, path}, conn, segments, range, options) do
222
233
%{
223
234
qs_cache: qs_cache,
 
@@ -230,10 +241,9 @@ defmodule Plug.Static do
230
241
case put_cache_header(conn, qs_cache, et_cache, et_generation, file_info, path) do
231
242
{:stale, conn} ->
232
243
filename = List.last(segments)
233
- content_type = Map.get(types, filename) || MIME.from_path(filename)
234
244
235
245
conn
236
- |> put_resp_header("content-type", content_type)
246
+ |> maybe_put_content_type(types, filename)
237
247
|> put_resp_header("accept-ranges", "bytes")
238
248
|> maybe_add_encoding(content_encoding)
239
249
|> merge_headers(headers)
changed mix.exs
 
@@ -1,7 +1,7 @@
1
1
defmodule Plug.MixProject do
2
2
use Mix.Project
3
3
4
- @version "1.15.3"
4
+ @version "1.16.0"
5
5
@description "Compose web applications with functions"
6
6
@xref_exclude [Plug.Cowboy, :ssl]