Skip to content

Commit b4aa8a0

Browse files
authored
Merge commit from fork
1 parent ac94702 commit b4aa8a0

2 files changed

Lines changed: 108 additions & 17 deletions

File tree

lib/plug/conn/query.ex

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ defmodule Plug.Conn.Query do
9090
@typedoc since: "1.16.0"
9191
@opaque decoder() :: map()
9292

93+
@max_nesting 32
94+
9395
@doc """
9496
Decodes the given `query`.
9597
@@ -204,32 +206,48 @@ defmodule Plug.Conn.Query do
204206
value = binary_part(key, 0, pos)
205207
pos = pos + 1
206208
rest = binary_part(key, pos, byte_size(key) - pos)
207-
split_keys(rest, key, pos, pos, value, [{:root, value}])
209+
split_keys(rest, key, pos, pos, value, [{:root, value}], 1)
208210
else
209211
_ -> [{:root, key}]
210212
end
211213

212214
insert_keys(keys, value, map)
213215
end
214216

215-
defp split_keys(<<?], ?[, rest::binary>>, binary, current_pos, start_pos, level, acc) do
217+
defp split_keys(<<?], ?[, rest::binary>>, binary, current_pos, start_pos, level, acc, count) do
218+
count = count + 1
219+
check_nesting!(count)
220+
216221
value = split_key(binary, current_pos, start_pos)
217222
next_level = binary_part(binary, 0, current_pos + 1)
218-
split_keys(rest, binary, current_pos + 2, current_pos + 2, next_level, [{level, value} | acc])
223+
current_pos = current_pos + 2
224+
acc = [{level, value} | acc]
225+
split_keys(rest, binary, current_pos, current_pos, next_level, acc, count)
219226
end
220227

221-
defp split_keys(<<?]>>, binary, current_pos, start_pos, level, acc) do
228+
defp split_keys(<<?]>>, binary, current_pos, start_pos, level, acc, count) do
229+
count = count + 1
230+
check_nesting!(count)
231+
222232
value = split_key(binary, current_pos, start_pos)
223233
[{level, value} | acc]
224234
end
225235

226-
defp split_keys(<<_, rest::binary>>, binary, current_pos, start_pos, level, acc) do
227-
split_keys(rest, binary, current_pos + 1, start_pos, level, acc)
236+
defp split_keys(<<_, rest::binary>>, binary, current_pos, start_pos, level, acc, count) do
237+
check_nesting!(count)
238+
split_keys(rest, binary, current_pos + 1, start_pos, level, acc, count)
228239
end
229240

230241
defp split_key(_binary, start, start), do: nil
231242
defp split_key(binary, current, start), do: binary_part(binary, start, current - start)
232243

244+
defp check_nesting!(count) when count > @max_nesting do
245+
message = "maximum query nesting is #{@max_nesting}, got a query with #{count} keys"
246+
raise Plug.Conn.InvalidQueryError, message: message
247+
end
248+
249+
defp check_nesting!(_count), do: :ok
250+
233251
defp insert_keys([{level, key} | rest], value, map) do
234252
case map do
235253
%{^level => entries} -> %{map | level => [{key, value} | entries]}
@@ -312,56 +330,67 @@ defmodule Plug.Conn.Query do
312330
#
313331
# users[address][street #=> [ "users", "address][street" ]
314332
#
315-
assign_split(:binary.split(subkey, "["), value, acc, :binary.compile_pattern("]["))
333+
pattern = :binary.compile_pattern("][")
334+
parts = :binary.split(subkey, "[")
335+
assign_split(parts, value, acc, pattern, 1)
316336
else
317337
assign_map(acc, key, value)
318338
end
319339
end
320340

321-
defp assign_split(["", rest], value, acc, pattern) do
341+
defp assign_split(["", rest], value, acc, pattern, count) do
342+
check_nesting!(count)
322343
parts = :binary.split(rest, pattern)
323344

324345
case acc do
325-
[_ | _] -> [assign_split(parts, value, :none, pattern) | acc]
326-
:none -> [assign_split(parts, value, :none, pattern)]
346+
[_ | _] -> [assign_split(parts, value, :none, pattern, count + 1) | acc]
347+
:none -> [assign_split(parts, value, :none, pattern, count + 1)]
327348
_ -> acc
328349
end
329350
end
330351

331-
defp assign_split([key, rest], value, acc, pattern) do
352+
defp assign_split([key, rest], value, acc, pattern, count) do
353+
check_nesting!(count)
332354
parts = :binary.split(rest, pattern)
333355

334356
case acc do
335357
%{^key => current} when is_list(current) or is_map(current) ->
336-
Map.put(acc, key, assign_split(parts, value, current, pattern))
358+
value = assign_split(parts, value, current, pattern, count + 1)
359+
Map.put(acc, key, value)
337360

338361
%{^key => _} ->
339362
acc
340363

341364
%{} ->
342-
Map.put(acc, key, assign_split(parts, value, :none, pattern))
365+
value = assign_split(parts, value, :none, pattern, count + 1)
366+
Map.put(acc, key, value)
343367

344368
_ ->
345-
%{key => assign_split(parts, value, :none, pattern)}
369+
%{key => assign_split(parts, value, :none, pattern, count + 1)}
346370
end
347371
end
348372

349-
defp assign_split([""], nil, acc, _pattern) do
373+
defp assign_split([""], nil, acc, _pattern, count) do
374+
check_nesting!(count)
375+
350376
case acc do
351377
[_ | _] -> acc
352378
_ -> []
353379
end
354380
end
355381

356-
defp assign_split([""], value, acc, _pattern) do
382+
defp assign_split([""], value, acc, _pattern, count) do
383+
check_nesting!(count)
384+
357385
case acc do
358386
[_ | _] -> [value | acc]
359387
:none -> [value]
360388
_ -> acc
361389
end
362390
end
363391

364-
defp assign_split([key], value, acc, _pattern) do
392+
defp assign_split([key], value, acc, _pattern, count) do
393+
check_nesting!(count)
365394
assign_map(acc, key, value)
366395
end
367396

test/plug/conn/query_test.exs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ defmodule Plug.Conn.QueryTest do
4545
assert decode("x[][][]=1") == %{"x" => [[["1"]]]}
4646
end
4747

48+
test "raises on queries nested more than 32 keys" do
49+
valid_key = "x" <> String.duplicate("[x]", 31)
50+
invalid_key = "x" <> String.duplicate("[x]", 32)
51+
message = "maximum query nesting is 32, got a query with 33 keys"
52+
53+
assert get_in(decode("#{valid_key}=1"), List.duplicate("x", 32)) == "1"
54+
55+
assert_raise Plug.Conn.InvalidQueryError, message, fn ->
56+
decode("#{invalid_key}=1")
57+
end
58+
end
59+
60+
test "raises on mixed list and map queries nested more than 32 keys" do
61+
valid_key = mixed_key(31)
62+
invalid_key = mixed_key(32)
63+
message = "maximum query nesting is 32, got a query with 33 keys"
64+
65+
assert get_in(decode("#{valid_key}=1"), mixed_path(31)) == "1"
66+
67+
assert_raise Plug.Conn.InvalidQueryError, message, fn ->
68+
decode("#{invalid_key}=1")
69+
end
70+
end
71+
4872
test "empty pairs" do
4973
assert decode("&x=1&&y=2&") == %{"x" => "1", "y" => "2"}
5074
end
@@ -180,10 +204,48 @@ defmodule Plug.Conn.QueryTest do
180204
assert params["foo"] == ["bar", "baz"]
181205
end
182206

207+
test "raises on queries nested more than 32 keys" do
208+
valid_key = "x" <> String.duplicate("[x]", 31)
209+
invalid_key = "x" <> String.duplicate("[x]", 32)
210+
message = "maximum query nesting is 32, got a query with 33 keys"
211+
212+
assert get_in(decode_pair([{valid_key, "1"}]), List.duplicate("x", 32)) == "1"
213+
214+
assert_raise Plug.Conn.InvalidQueryError, message, fn ->
215+
decode_pair([{invalid_key, "1"}])
216+
end
217+
end
218+
219+
test "raises on mixed list and map queries nested more than 32 keys" do
220+
valid_key = mixed_key(31)
221+
invalid_key = mixed_key(32)
222+
message = "maximum query nesting is 32, got a query with 33 keys"
223+
224+
assert get_in(decode_pair([{valid_key, "1"}]), mixed_path(31)) == "1"
225+
226+
assert_raise Plug.Conn.InvalidQueryError, message, fn ->
227+
decode_pair([{invalid_key, "1"}])
228+
end
229+
end
230+
183231
defp decode_pair(pairs) do
184232
pairs
185233
|> Enum.reduce(Plug.Conn.Query.decode_init(), &Plug.Conn.Query.decode_each/2)
186234
|> Plug.Conn.Query.decode_done()
187235
end
188236
end
237+
238+
defp mixed_key(n) do
239+
Enum.reduce(1..n, "a", fn
240+
index, acc when rem(index, 2) == 1 -> acc <> "[]"
241+
_index, acc -> acc <> "[foo]"
242+
end)
243+
end
244+
245+
defp mixed_path(n) do
246+
Enum.reduce(1..n, ["a"], fn
247+
index, acc when rem(index, 2) == 1 -> acc ++ [Access.at(0)]
248+
_index, acc -> acc ++ ["foo"]
249+
end)
250+
end
189251
end

0 commit comments

Comments
 (0)