1defmodule Plug.Conn.Query do
2  @moduledoc """
3  Conveniences for decoding and encoding url encoded queries.
4
5  Plug allows a developer to build query strings
6  that map to Elixir structures in order to make
7  manipulation of such structures easier on the server
8  side. Here are some examples:
9
10      iex> decode("foo=bar")["foo"]
11      "bar"
12
13  If a value is given more than once, the last value takes precedence:
14
15      iex> decode("foo=bar&foo=baz")["foo"]
16      "baz"
17
18  Nested structures can be created via `[key]`:
19
20      iex> decode("foo[bar]=baz")["foo"]["bar"]
21      "baz"
22
23  Lists are created with `[]`:
24
25      iex> decode("foo[]=bar&foo[]=baz")["foo"]
26      ["bar", "baz"]
27
28  Maps can be encoded:
29
30      iex> encode(%{foo: "bar", baz: "bat"})
31      "baz=bat&foo=bar"
32
33  Encoding keyword lists preserves the order of the fields:
34
35      iex> encode([foo: "bar", baz: "bat"])
36      "foo=bar&baz=bat"
37
38  When encoding keyword lists with duplicate keys, the key that comes first
39  takes precedence:
40
41      iex> encode([foo: "bar", foo: "bat"])
42      "foo=bar"
43
44  Encoding named lists:
45
46      iex> encode(%{foo: ["bar", "baz"]})
47      "foo[]=bar&foo[]=baz"
48
49  Encoding nested structures:
50
51      iex> encode(%{foo: %{bar: "baz"}})
52      "foo[bar]=baz"
53
54  """
55
56  @doc """
57  Decodes the given binary.
58  """
59  def decode(query, initial \\ %{})
60
61  def decode("", initial) do
62    initial
63  end
64
65  def decode(query, initial) do
66    parts = :binary.split(query, "&", [:global])
67    Enum.reduce(Enum.reverse(parts), initial, &decode_string_pair(&1, &2))
68  end
69
70  defp decode_string_pair(binary, acc) do
71    current =
72      case :binary.split(binary, "=") do
73        [key, value] ->
74          {decode_www_form(key), decode_www_form(value)}
75        [key] ->
76          {decode_www_form(key), nil}
77      end
78
79    decode_pair(current, acc)
80  end
81
82  defp decode_www_form(value) do
83    try do
84      URI.decode_www_form(value)
85    rescue
86      ArgumentError ->
87        raise Plug.Conn.InvalidQueryError,
88          message: "invalid www-form encoding on query-string, got #{value}"
89    end
90  end
91
92  @doc """
93  Decodes the given tuple and stores it in the accumulator.
94  It parses the key and stores the value into the current
95  accumulator.
96
97  Parameter lists are added to the accumulator in reverse
98  order, so be sure to pass the parameters in reverse order.
99  """
100  def decode_pair({key, value}, acc) do
101    if key != "" and :binary.last(key) == ?] do
102      # Remove trailing ]
103      subkey = :binary.part(key, 0, byte_size(key) - 1)
104
105      # Split the first [ then we will split on remaining ][.
106      #
107      #     users[address][street #=> [ "users", "address][street" ]
108      #
109      assign_split(:binary.split(subkey, "["), value, acc, :binary.compile_pattern("]["))
110    else
111      assign_map(acc, key, value)
112    end
113  end
114
115  defp assign_split(["", rest], value, acc, pattern) do
116    parts = :binary.split(rest, pattern)
117
118    case acc do
119      [_ | _] -> [assign_split(parts, value, :none, pattern) | acc]
120      :none -> [assign_split(parts, value, :none, pattern)]
121      _ -> acc
122    end
123  end
124
125  defp assign_split([key, rest], value, acc, pattern) do
126    parts = :binary.split(rest, pattern)
127
128    case acc do
129      %{^key => current} ->
130        Map.put(acc, key, assign_split(parts, value, current, pattern))
131      %{} ->
132        Map.put(acc, key, assign_split(parts, value, :none, pattern))
133      _ ->
134        %{key => assign_split(parts, value, :none, pattern)}
135    end
136  end
137
138  defp assign_split([""], nil, acc, _pattern) do
139    case acc do
140      [_ | _] -> acc
141      _ -> []
142    end
143  end
144
145  defp assign_split([""], value, acc, _pattern) do
146    case acc do
147      [_ | _] -> [value | acc]
148      :none -> [value]
149      _ -> acc
150    end
151  end
152
153  defp assign_split([key], value, acc, _pattern) do
154    assign_map(acc, key, value)
155  end
156
157  defp assign_map(acc, key, value) do
158    case acc do
159      %{^key => _} -> acc
160      %{} -> Map.put(acc, key, value)
161      _ -> %{key => value}
162    end
163  end
164
165  @doc """
166  Encodes the given map or list of tuples.
167  """
168  def encode(kv, encoder \\ &to_string/1) do
169    IO.iodata_to_binary encode_pair("", kv, encoder)
170  end
171
172  # covers structs
173  defp encode_pair(field, %{__struct__: struct} = map, encoder) when is_atom(struct) do
174    [field, ?= | encode_value(map, encoder)]
175  end
176
177  # covers maps
178  defp encode_pair(parent_field, %{} = map, encoder) do
179    encode_kv(map, parent_field, encoder)
180  end
181
182  # covers keyword lists
183  defp encode_pair(parent_field, list, encoder) when is_list(list) and is_tuple(hd(list)) do
184    encode_kv(Enum.uniq_by(list, &elem(&1, 0)), parent_field, encoder)
185  end
186
187  # covers non-keyword lists
188  defp encode_pair(parent_field, list, encoder) when is_list(list) do
189    prune Enum.flat_map list, fn
190      value when is_map(value) and map_size(value) != 1 ->
191        raise ArgumentError,
192          "cannot encode maps inside lists when the map has 0 or more than 1 elements, " <>
193          "got: #{inspect(value)}"
194      value ->
195        [?&, encode_pair(parent_field <> "[]", value, encoder)]
196    end
197  end
198
199  # covers nil
200  defp encode_pair(field, nil, _encoder) do
201    [field, ?=]
202  end
203
204  # encoder fallback
205  defp encode_pair(field, value, encoder) do
206    [field, ?=|encode_value(value, encoder)]
207  end
208
209  defp encode_kv(kv, parent_field, encoder) do
210    prune Enum.flat_map kv, fn
211      {_, value} when value in [%{}, []] ->
212        []
213      {field, value} ->
214        field =
215          if parent_field == "" do
216            encode_key(field)
217          else
218            parent_field <> "[" <> encode_key(field) <> "]"
219          end
220        [?&, encode_pair(field, value, encoder)]
221    end
222  end
223
224  defp encode_key(item) do
225    item |> to_string |> URI.encode_www_form
226  end
227
228  defp encode_value(item, encoder) do
229    item |> encoder.() |> URI.encode_www_form
230  end
231
232  defp prune([?&|t]), do: t
233  defp prune([]), do: []
234end
235