cookies.ex (4069B)
1 defmodule Plug.Conn.Cookies do 2 @moduledoc """ 3 Conveniences for encoding and decoding cookies. 4 """ 5 6 @doc """ 7 Decodes the given cookies as given in either a request or response header. 8 9 If a cookie is invalid, it is automatically discarded from the result. 10 11 ## Examples 12 13 iex> decode("key1=value1;key2=value2") 14 %{"key1" => "value1", "key2" => "value2"} 15 16 """ 17 def decode(cookie) do 18 do_decode(:binary.split(cookie, ";", [:global]), %{}) 19 end 20 21 defp do_decode([], acc), do: acc 22 23 defp do_decode([h | t], acc) do 24 case decode_kv(h) do 25 {k, v} -> do_decode(t, Map.put(acc, k, v)) 26 false -> do_decode(t, acc) 27 end 28 end 29 30 defp decode_kv(""), do: false 31 defp decode_kv(<<h, t::binary>>) when h in [?\s, ?\t], do: decode_kv(t) 32 defp decode_kv(kv), do: decode_key(kv, "") 33 34 defp decode_key("", _key), do: false 35 defp decode_key(<<?=, _::binary>>, ""), do: false 36 defp decode_key(<<?=, t::binary>>, key), do: decode_value(t, "", key, "") 37 defp decode_key(<<h, _::binary>>, _key) when h in [?\s, ?\t, ?\r, ?\n, ?\v, ?\f], do: false 38 defp decode_key(<<h, t::binary>>, key), do: decode_key(t, <<key::binary, h>>) 39 40 defp decode_value("", _spaces, key, value), do: {key, value} 41 42 defp decode_value(<<?\s, t::binary>>, spaces, key, value), 43 do: decode_value(t, <<spaces::binary, ?\s>>, key, value) 44 45 defp decode_value(<<h, _::binary>>, _spaces, _key, _value) when h in [?\t, ?\r, ?\n, ?\v, ?\f], 46 do: false 47 48 defp decode_value(<<h, t::binary>>, spaces, key, value), 49 do: decode_value(t, "", key, <<value::binary, spaces::binary, h>>) 50 51 @doc """ 52 Encodes the given cookies as expected in a response header. 53 """ 54 def encode(key, opts \\ %{}) when is_map(opts) do 55 value = Map.get(opts, :value) 56 path = Map.get(opts, :path, "/") 57 58 IO.iodata_to_binary([ 59 "#{key}=#{value}; path=#{path}", 60 emit_if(opts[:domain], &["; domain=", &1]), 61 emit_if(opts[:max_age], &encode_max_age(&1, opts)), 62 emit_if(Map.get(opts, :secure, false), "; secure"), 63 emit_if(Map.get(opts, :http_only, true), "; HttpOnly"), 64 emit_if(Map.get(opts, :same_site, nil), &encode_same_site/1), 65 emit_if(opts[:extra], &["; ", &1]) 66 ]) 67 end 68 69 defp encode_max_age(max_age, opts) do 70 time = Map.get(opts, :universal_time) || :calendar.universal_time() 71 time = add_seconds(time, max_age) 72 ["; expires=", rfc2822(time), "; max-age=", Integer.to_string(max_age)] 73 end 74 75 defp encode_same_site(value) when is_binary(value), do: "; SameSite=#{value}" 76 77 defp emit_if(value, fun_or_string) do 78 cond do 79 !value -> 80 [] 81 82 is_function(fun_or_string) -> 83 fun_or_string.(value) 84 85 is_binary(fun_or_string) -> 86 fun_or_string 87 end 88 end 89 90 defp pad(number) when number in 0..9, do: <<?0, ?0 + number>> 91 defp pad(number), do: Integer.to_string(number) 92 93 defp rfc2822({{year, month, day} = date, {hour, minute, second}}) do 94 # Sat, 17 Apr 2010 14:00:00 GMT 95 [ 96 weekday_name(:calendar.day_of_the_week(date)), 97 ?,, 98 ?\s, 99 pad(day), 100 ?\s, 101 month_name(month), 102 ?\s, 103 Integer.to_string(year), 104 ?\s, 105 pad(hour), 106 ?:, 107 pad(minute), 108 ?:, 109 pad(second), 110 " GMT" 111 ] 112 end 113 114 defp weekday_name(1), do: "Mon" 115 defp weekday_name(2), do: "Tue" 116 defp weekday_name(3), do: "Wed" 117 defp weekday_name(4), do: "Thu" 118 defp weekday_name(5), do: "Fri" 119 defp weekday_name(6), do: "Sat" 120 defp weekday_name(7), do: "Sun" 121 122 defp month_name(1), do: "Jan" 123 defp month_name(2), do: "Feb" 124 defp month_name(3), do: "Mar" 125 defp month_name(4), do: "Apr" 126 defp month_name(5), do: "May" 127 defp month_name(6), do: "Jun" 128 defp month_name(7), do: "Jul" 129 defp month_name(8), do: "Aug" 130 defp month_name(9), do: "Sep" 131 defp month_name(10), do: "Oct" 132 defp month_name(11), do: "Nov" 133 defp month_name(12), do: "Dec" 134 135 defp add_seconds(time, seconds_to_add) do 136 time_seconds = :calendar.datetime_to_gregorian_seconds(time) 137 :calendar.gregorian_seconds_to_datetime(time_seconds + seconds_to_add) 138 end 139 end