Skip to main content

lib/mint/http1/parse.ex

defmodule Mint.HTTP1.Parse do
  @moduledoc false

  defmacro is_digit(char), do: quote(do: unquote(char) in ?0..?9)
  defmacro is_alpha(char), do: quote(do: unquote(char) in ?a..?z or unquote(char) in ?A..?Z)
  defmacro is_whitespace(char), do: quote(do: unquote(char) in '\s\t')
  defmacro is_comma(char), do: quote(do: unquote(char) == ?,)
  defmacro is_vchar(char), do: quote(do: unquote(char) in 33..126)

  defmacro is_tchar(char) do
    quote do
      is_digit(unquote(char)) or is_alpha(unquote(char)) or unquote(char) in '!#$%&\'*+-.^_`|~'
    end
  end

  defp lower_char(?A), do: ?a
  defp lower_char(?B), do: ?b
  defp lower_char(?C), do: ?c
  defp lower_char(?D), do: ?d
  defp lower_char(?E), do: ?e
  defp lower_char(?F), do: ?f
  defp lower_char(?G), do: ?g
  defp lower_char(?H), do: ?h
  defp lower_char(?I), do: ?i
  defp lower_char(?J), do: ?j
  defp lower_char(?K), do: ?k
  defp lower_char(?L), do: ?l
  defp lower_char(?M), do: ?m
  defp lower_char(?N), do: ?n
  defp lower_char(?O), do: ?o
  defp lower_char(?P), do: ?p
  defp lower_char(?Q), do: ?q
  defp lower_char(?R), do: ?r
  defp lower_char(?S), do: ?s
  defp lower_char(?T), do: ?t
  defp lower_char(?U), do: ?u
  defp lower_char(?V), do: ?v
  defp lower_char(?W), do: ?w
  defp lower_char(?X), do: ?x
  defp lower_char(?Y), do: ?y
  defp lower_char(?Z), do: ?z
  defp lower_char(char), do: char

  def lower(string), do: for(<<char <- string>>, do: <<lower_char(char)>>, into: "")

  def ignore_until_crlf(<<>>), do: :more
  def ignore_until_crlf(<<"\r\n", rest::binary>>), do: {:ok, rest}
  def ignore_until_crlf(<<_char, rest::binary>>), do: ignore_until_crlf(rest)

  def content_length_header(string) do
    case Integer.parse(string) do
      {length, ""} when length >= 0 ->
        length

      _other ->
        throw({:mint, :invalid_content_length_header})
    end
  end

  def connection_header(string) do
    string
    |> token_list_downcase()
    |> not_empty!()
  end

  def transfer_encoding_header(string) do
    string
    |> token_list_downcase()
    |> not_empty!()
  end

  def token_list_downcase(string), do: token_list_downcase(string, [])

  defp token_list_downcase(<<>>, acc), do: :lists.reverse(acc)

  defp token_list_downcase(<<char, rest::binary>>, acc)
       when is_whitespace(char) or is_comma(char),
       do: token_list_downcase(rest, acc)

  defp token_list_downcase(rest, acc), do: token_downcase(rest, <<>>, acc)

  defp token_downcase(<<char, rest::binary>>, token, acc) when is_tchar(char),
    do: token_downcase(rest, <<token::binary, lower_char(char)>>, acc)

  # defp token_downcase(_rest, <<>>, _acc), do: throw({:mint, :invalid_token})

  defp token_downcase(rest, token, acc), do: token_list_sep_downcase(rest, [token | acc])

  defp token_list_sep_downcase(<<>>, acc), do: :lists.reverse(acc)

  defp token_list_sep_downcase(<<char, rest::binary>>, acc) when is_whitespace(char),
    do: token_list_sep_downcase(rest, acc)

  defp token_list_sep_downcase(<<?,, rest::binary>>, acc), do: token_list_downcase(rest, acc)

  defp token_list_sep_downcase(_rest, _acc), do: throw({:mint, :invalid_token_list})

  def token_list(string), do: token_list(string, [])

  defp token_list(<<>>, acc), do: :lists.reverse(acc)

  defp token_list(<<char, rest::binary>>, acc) when is_whitespace(char) or is_comma(char),
    do: token_list(rest, acc)

  defp token_list(rest, acc), do: token(rest, <<>>, acc)

  defp token(<<char, rest::binary>>, token, acc) when is_tchar(char),
    do: token(rest, <<token::binary, char>>, acc)

  # defp token(_rest, <<>>, _acc), do: throw({:mint, :invalid_token})

  defp token(rest, token, acc), do: token_list_sep(rest, [token | acc])

  defp token_list_sep(<<>>, acc), do: :lists.reverse(acc)

  defp token_list_sep(<<char, rest::binary>>, acc) when is_whitespace(char),
    do: token_list_sep(rest, acc)

  defp token_list_sep(<<?,, rest::binary>>, acc), do: token_list(rest, acc)

  defp token_list_sep(_rest, _acc), do: throw({:mint, :invalid_token_list})

  defp not_empty!([]), do: throw({:mint, :empty_token_list})

  defp not_empty!(list), do: list
end