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