zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

utils.ex (8282B)


      1 defmodule Plug.Conn.Utils do
      2   @moduledoc """
      3   Utilities for working with connection data
      4   """
      5 
      6   @type params :: %{optional(binary) => binary}
      7 
      8   @upper ?A..?Z
      9   @lower ?a..?z
     10   @alpha ?0..?9
     11   @other [?., ?-, ?+]
     12   @space [?\s, ?\t]
     13   @specials ~c|()<>@,;:\\"/[]?={}|
     14 
     15   @doc ~S"""
     16   Parses media types (with wildcards).
     17 
     18   Type and subtype are case insensitive while the
     19   sensitiveness of params depends on their keys and
     20   therefore are not handled by this parser.
     21 
     22   Returns:
     23 
     24     * `{:ok, type, subtype, map_of_params}` if everything goes fine
     25     * `:error` if the media type isn't valid
     26 
     27   ## Examples
     28 
     29       iex> media_type "text/plain"
     30       {:ok, "text", "plain", %{}}
     31 
     32       iex> media_type "APPLICATION/vnd.ms-data+XML"
     33       {:ok, "application", "vnd.ms-data+xml", %{}}
     34 
     35       iex> media_type "text/*; q=1.0"
     36       {:ok, "text", "*", %{"q" => "1.0"}}
     37 
     38       iex> media_type "*/*; q=1.0"
     39       {:ok, "*", "*", %{"q" => "1.0"}}
     40 
     41       iex> media_type "x y"
     42       :error
     43 
     44       iex> media_type "*/html"
     45       :error
     46 
     47       iex> media_type "/"
     48       :error
     49 
     50       iex> media_type "x/y z"
     51       :error
     52 
     53   """
     54   @spec media_type(binary) :: {:ok, type :: binary, subtype :: binary, params} | :error
     55   def media_type(binary) do
     56     case strip_spaces(binary) do
     57       "*/*" <> t -> mt_params(t, "*", "*")
     58       t -> mt_first(t, "")
     59     end
     60   end
     61 
     62   defp mt_first(<<?/, t::binary>>, acc) when acc != "", do: mt_wildcard(t, acc)
     63 
     64   defp mt_first(<<h, t::binary>>, acc) when h in @upper,
     65     do: mt_first(t, <<acc::binary, downcase_char(h)>>)
     66 
     67   defp mt_first(<<h, t::binary>>, acc) when h in @lower or h in @alpha or h == ?-,
     68     do: mt_first(t, <<acc::binary, h>>)
     69 
     70   defp mt_first(_, _acc), do: :error
     71 
     72   defp mt_wildcard(<<?*, t::binary>>, first), do: mt_params(t, first, "*")
     73   defp mt_wildcard(t, first), do: mt_second(t, "", first)
     74 
     75   defp mt_second(<<h, t::binary>>, acc, first) when h in @upper,
     76     do: mt_second(t, <<acc::binary, downcase_char(h)>>, first)
     77 
     78   defp mt_second(<<h, t::binary>>, acc, first) when h in @lower or h in @alpha or h in @other,
     79     do: mt_second(t, <<acc::binary, h>>, first)
     80 
     81   defp mt_second(t, acc, first), do: mt_params(t, first, acc)
     82 
     83   defp mt_params(t, first, second) do
     84     case strip_spaces(t) do
     85       "" -> {:ok, first, second, %{}}
     86       ";" <> t -> {:ok, first, second, params(t)}
     87       _ -> :error
     88     end
     89   end
     90 
     91   @doc ~S"""
     92   Parses content type (without wildcards).
     93 
     94   It is similar to `media_type/1` except wildcards are
     95   not accepted in the type nor in the subtype.
     96 
     97   ## Examples
     98 
     99       iex> content_type "x-sample/json; charset=utf-8"
    100       {:ok, "x-sample", "json", %{"charset" => "utf-8"}}
    101 
    102       iex> content_type "x-sample/json  ; charset=utf-8  ; foo=bar"
    103       {:ok, "x-sample", "json", %{"charset" => "utf-8", "foo" => "bar"}}
    104 
    105       iex> content_type "\r\n text/plain;\r\n charset=utf-8\r\n"
    106       {:ok, "text", "plain", %{"charset" => "utf-8"}}
    107 
    108       iex> content_type "text/plain"
    109       {:ok, "text", "plain", %{}}
    110 
    111       iex> content_type "x/*"
    112       :error
    113 
    114       iex> content_type "*/*"
    115       :error
    116 
    117   """
    118   @spec content_type(binary) :: {:ok, type :: binary, subtype :: binary, params} | :error
    119   def content_type(binary) do
    120     case media_type(binary) do
    121       {:ok, _, "*", _} -> :error
    122       {:ok, _, _, _} = ok -> ok
    123       :error -> :error
    124     end
    125   end
    126 
    127   @doc ~S"""
    128   Parses headers parameters.
    129 
    130   Keys are case insensitive and downcased,
    131   invalid key-value pairs are discarded.
    132 
    133   ## Examples
    134 
    135       iex> params("foo=bar")
    136       %{"foo" => "bar"}
    137 
    138       iex> params("  foo=bar  ")
    139       %{"foo" => "bar"}
    140 
    141       iex> params("FOO=bar")
    142       %{"foo" => "bar"}
    143 
    144       iex> params("Foo=bar; baz=BOING")
    145       %{"foo" => "bar", "baz" => "BOING"}
    146 
    147       iex> params("foo=BAR ; wat")
    148       %{"foo" => "BAR"}
    149 
    150       iex> params("foo=\"bar\"; baz=\"boing\"")
    151       %{"foo" => "bar", "baz" => "boing"}
    152 
    153       iex> params("foo=\"bar;\"; baz=\"boing\"")
    154       %{"foo" => "bar;", "baz" => "boing"}
    155 
    156       iex> params("=")
    157       %{}
    158 
    159       iex> params(";")
    160       %{}
    161 
    162   """
    163   @spec params(binary) :: params
    164   def params(t) do
    165     t
    166     |> split_semicolon("", [], false)
    167     |> Enum.reduce(%{}, &params/2)
    168   end
    169 
    170   defp params(param, acc) do
    171     case params_key(strip_spaces(param), "") do
    172       {k, v} -> Map.put(acc, k, v)
    173       false -> acc
    174     end
    175   end
    176 
    177   defp params_key(<<?=, t::binary>>, acc) when acc != "", do: params_value(t, acc)
    178 
    179   defp params_key(<<h, _::binary>>, _acc)
    180        when h in @specials or h in @space or h < 32 or h === 127,
    181        do: false
    182 
    183   defp params_key(<<h, t::binary>>, acc), do: params_key(t, <<acc::binary, downcase_char(h)>>)
    184   defp params_key(<<>>, _acc), do: false
    185 
    186   defp params_value(token, key) do
    187     case token(token) do
    188       false -> false
    189       value -> {key, value}
    190     end
    191   end
    192 
    193   @doc ~S"""
    194   Parses a value as defined in [RFC-1341](http://www.w3.org/Protocols/rfc1341/4_Content-Type.html).
    195 
    196   For convenience, trims whitespace at the end of the token.
    197   Returns `false` if the token is invalid.
    198 
    199   ## Examples
    200 
    201       iex> token("foo")
    202       "foo"
    203 
    204       iex> token("foo-bar")
    205       "foo-bar"
    206 
    207       iex> token("<foo>")
    208       false
    209 
    210       iex> token(~s["<foo>"])
    211       "<foo>"
    212 
    213       iex> token(~S["<f\oo>\"<b\ar>"])
    214       "<foo>\"<bar>"
    215 
    216       iex> token(~s["])
    217       false
    218 
    219       iex> token("foo  ")
    220       "foo"
    221 
    222       iex> token("foo bar")
    223       false
    224 
    225       iex> token("")
    226       false
    227 
    228       iex> token(" ")
    229       ""
    230 
    231   """
    232   @spec token(binary) :: binary | false
    233   def token(""), do: false
    234   def token(<<?", quoted::binary>>), do: quoted_token(quoted, "")
    235   def token(token), do: unquoted_token(token, "")
    236 
    237   defp quoted_token(<<>>, _acc), do: false
    238   defp quoted_token(<<?", t::binary>>, acc), do: strip_spaces(t) == "" and acc
    239   defp quoted_token(<<?\\, h, t::binary>>, acc), do: quoted_token(t, <<acc::binary, h>>)
    240   defp quoted_token(<<h, t::binary>>, acc), do: quoted_token(t, <<acc::binary, h>>)
    241 
    242   defp unquoted_token(<<>>, acc), do: acc
    243   defp unquoted_token("\r\n" <> t, acc), do: strip_spaces(t) == "" and acc
    244   defp unquoted_token(<<h, t::binary>>, acc) when h in @space, do: strip_spaces(t) == "" and acc
    245 
    246   defp unquoted_token(<<h, _::binary>>, _acc) when h in @specials or h < 32 or h === 127,
    247     do: false
    248 
    249   defp unquoted_token(<<h, t::binary>>, acc), do: unquoted_token(t, <<acc::binary, h>>)
    250 
    251   @doc """
    252   Parses a comma-separated list of header values.
    253 
    254   ## Examples
    255 
    256       iex> list("foo, bar")
    257       ["foo", "bar"]
    258 
    259       iex> list("foobar")
    260       ["foobar"]
    261 
    262       iex> list("")
    263       []
    264 
    265       iex> list("empties, , are,, filtered")
    266       ["empties", "are", "filtered"]
    267 
    268   """
    269   @spec list(binary) :: [binary]
    270   def list(binary) do
    271     for elem <- :binary.split(binary, ",", [:global]),
    272         stripped = strip_spaces(elem),
    273         stripped != "",
    274         do: stripped
    275   end
    276 
    277   @doc """
    278   Validates the given binary is valid UTF-8.
    279   """
    280   @spec validate_utf8!(binary, module, binary) :: :ok | no_return
    281   def validate_utf8!(binary, exception, context)
    282 
    283   def validate_utf8!(<<binary::binary>>, exception, context) do
    284     do_validate_utf8!(binary, exception, context)
    285   end
    286 
    287   defp do_validate_utf8!(<<_::utf8, rest::bits>>, exception, context) do
    288     do_validate_utf8!(rest, exception, context)
    289   end
    290 
    291   defp do_validate_utf8!(<<byte, _::bits>>, exception, context) do
    292     raise exception, "invalid UTF-8 on #{context}, got byte #{byte}"
    293   end
    294 
    295   defp do_validate_utf8!(<<>>, _exception, _context) do
    296     :ok
    297   end
    298 
    299   ## Helpers
    300 
    301   defp strip_spaces("\r\n" <> t), do: strip_spaces(t)
    302   defp strip_spaces(<<h, t::binary>>) when h in [?\s, ?\t], do: strip_spaces(t)
    303   defp strip_spaces(t), do: t
    304 
    305   defp downcase_char(char) when char in @upper, do: char + 32
    306   defp downcase_char(char), do: char
    307 
    308   defp split_semicolon(<<>>, <<>>, acc, _), do: acc
    309   defp split_semicolon(<<>>, buffer, acc, _), do: [buffer | acc]
    310 
    311   defp split_semicolon(<<?", rest::binary>>, buffer, acc, quoted?),
    312     do: split_semicolon(rest, <<buffer::binary, ?">>, acc, not quoted?)
    313 
    314   defp split_semicolon(<<?;, rest::binary>>, buffer, acc, false),
    315     do: split_semicolon(rest, <<>>, [buffer | acc], false)
    316 
    317   defp split_semicolon(<<char, rest::binary>>, buffer, acc, quoted?),
    318     do: split_semicolon(rest, <<buffer::binary, char>>, acc, quoted?)
    319 end