Skip to content

Commit c422454

Browse files
authored
Allow colon to be escaped when building the path segments (#1287)
1 parent 853f142 commit c422454

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

lib/plug/router.ex

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ defmodule Plug.Router do
7474
The above will match `/hello/foo.json` but not `/hello/foo`.
7575
Other delimiters such as `-`, `@` may be used to denote suffixes.
7676
77+
Identifier matching can be escaped using the `\` character:
78+
79+
get "/hello/\\:greet" do
80+
send_resp(conn, 200, "hello")
81+
end
82+
83+
The above will only match `/hello/:greet`.
84+
7785
Routes allow for globbing which will match the remaining parts
7886
of a route. A glob match is done with the `*` character followed
7987
by the variable name. Typically you prefix the variable name with
@@ -90,6 +98,15 @@ defmodule Plug.Router do
9098
send_resp(conn, 200, "route after /hello: #{inspect glob}")
9199
end
92100
101+
Similarly to `:identifiers`, globs are also escaped using the
102+
`\` character:
103+
104+
get "/hello/\\*glob" do
105+
send_resp(conn, 200, "this is not a glob route")
106+
end
107+
108+
The above will only match `/hello/*glob`.
109+
93110
Opposite to `:identifiers`, globs do not allow prefix nor suffix
94111
matches.
95112

lib/plug/router/utils.ex

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ defmodule Plug.Router.Utils do
127127
including the known parameters.
128128
"""
129129
def build_path_clause(path, guard, context \\ nil) when is_binary(path) do
130-
compiled = :binary.compile_pattern([":", "*"])
130+
compiled = :binary.compile_pattern(["\\:", "\\*", ":", "*"])
131131

132132
{params, match, guards, post_match} =
133133
path
@@ -148,6 +148,15 @@ defmodule Plug.Router.Utils do
148148
[] ->
149149
build_path_clause(rest, params, [segment | match], guards, post_match, context, compiled)
150150

151+
[{prefix_size, match_length}] when match_length == 2 ->
152+
suffix_size = byte_size(segment) - prefix_size - 2
153+
154+
<<prefix::binary-size(prefix_size), ?\\, char, suffix::binary-size(suffix_size)>> =
155+
segment
156+
157+
escaped_segment = [prefix <> <<char>> <> suffix | match]
158+
build_path_clause(rest, params, escaped_segment, guards, post_match, context, compiled)
159+
151160
[{prefix_size, _}] ->
152161
suffix_size = byte_size(segment) - prefix_size - 1
153162
<<prefix::binary-size(prefix_size), char, suffix::binary-size(suffix_size)>> = segment

test/plug/router/utils_test.exs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ defmodule Plug.Router.UtilsTest do
6060
build_path_match("foo/bar:username")
6161
end
6262

63+
test "build match with escaped identifiers" do
64+
assert quote(@opts, do: {[], ["foo", ":"]}) == build_path_match("foo/\\:")
65+
assert quote(@opts, do: {[], ["foo", ":id"]}) == build_path_match("/foo/\\:id")
66+
assert quote(@opts, do: {[], ["foo", ":username"]}) == build_path_match("foo/\\:username")
67+
68+
assert quote(@opts, do: {[:id, :post_id], ["foo", id, ":name", post_id]}) ==
69+
build_path_match("/foo/:id/\\:name/:post_id")
70+
71+
assert quote(@opts, do: {[], ["foo", "bar-:id"]}) == build_path_match("/foo/bar-\\:id")
72+
73+
assert quote(@opts, do: {[], ["foo", "bar:batchDelete"]}) ==
74+
build_path_match("foo/bar\\:batchDelete")
75+
end
76+
6377
test "build match only with glob" do
6478
assert quote(@opts, do: {[:bar], bar}) == build_path_match("*bar")
6579
assert quote(@opts, do: {[:glob], glob}) == build_path_match("/*glob")
@@ -70,6 +84,14 @@ defmodule Plug.Router.UtilsTest do
7084
assert quote(@opts, do: {[:glob], ["foo" | glob]}) == build_path_match("foo/*glob")
7185
end
7286

87+
test "build match with escaped glob" do
88+
assert quote(@opts, do: {[], ["*bar"]}) == build_path_match("\\*bar")
89+
assert quote(@opts, do: {[], ["*glob"]}) == build_path_match("/\\*glob")
90+
91+
assert quote(@opts, do: {[], ["foo", "*bar"]}) == build_path_match("/foo/\\*bar")
92+
assert quote(@opts, do: {[], ["foo", "*glob"]}) == build_path_match("foo/\\*glob")
93+
end
94+
7395
test "build invalid match with empty matches" do
7496
assert_raise Plug.Router.InvalidSpecError,
7597
"invalid dynamic path. The characters : and * must be immediately followed by lowercase letters or underscore, got: :",

0 commit comments

Comments
 (0)