diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index a9d4f542c2a..6615b5b6743 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -319,13 +319,77 @@ defmodule Base do "Double check your string for unwanted characters or pad it accordingly" end + @doc """ + Checks if a string is a valid base 16 encoded string. + + > #### When to use this {: .tip} + > + > Use this function when you just need to *validate* that a string is + > valid base 16 data, without actually producing a decoded output string. + > This function is both more performant and memory efficient than using + > `decode16/2`, checking that the result is `{:ok, ...}`, and then + > discarding the decoded binary. + + ## Options + + Accepts the same options as `decode16/2`. + + ## Examples + + iex> Base.valid16?("666F6F626172") + true + + iex> Base.valid16?("666f6f626172", case: :lower) + true + + iex> Base.valid16?("666f6F626172", case: :mixed) + true + + iex> Base.valid16?("ff", case: :upper) + false + + """ + @doc since: "1.19.0" + @spec valid16?(binary, case: decode_case) :: boolean + def valid16?(string, opts \\ []) + + def valid16?(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do + case Keyword.get(opts, :case, :upper) do + :upper -> validate16upper!(string) + :lower -> validate16lower!(string) + :mixed -> validate16mixed!(string) + end + + true + rescue + ArgumentError -> false + end + + def valid16?(string, _opts) when is_binary(string) do + false + end + upper = Enum.with_index(b16_alphabet) for {base, alphabet} <- [upper: upper, lower: to_lower_dec.(upper), mixed: to_mixed_dec.(upper)] do - name = :"decode16#{base}!" + decode_name = :"decode16#{base}!" + validate_name = :"validate16#{base}!" + {min, decoded} = to_decode_list.(alphabet) - defp unquote(name)(char) do + defp unquote(validate_name)(<<>>), do: :ok + + defp unquote(validate_name)(<>) do + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(validate_name)(rest) + end + + defp unquote(validate_name)(<>) do + bad_character!(char) + end + + defp unquote(decode_name)(char) do try do elem({unquote_splicing(decoded)}, char - unquote(min)) rescue @@ -336,41 +400,44 @@ defmodule Base do end end - defp unquote(name)(<>, acc) do - unquote(name)( + defp unquote(decode_name)(<>, acc) do + unquote(decode_name)( rest, << acc::binary, - unquote(name)(c1)::4, - unquote(name)(c2)::4, - unquote(name)(c3)::4, - unquote(name)(c4)::4, - unquote(name)(c5)::4, - unquote(name)(c6)::4, - unquote(name)(c7)::4, - unquote(name)(c8)::4 + unquote(decode_name)(c1)::4, + unquote(decode_name)(c2)::4, + unquote(decode_name)(c3)::4, + unquote(decode_name)(c4)::4, + unquote(decode_name)(c5)::4, + unquote(decode_name)(c6)::4, + unquote(decode_name)(c7)::4, + unquote(decode_name)(c8)::4 >> ) end - defp unquote(name)(<>, acc) do - unquote(name)( + defp unquote(decode_name)(<>, acc) do + unquote(decode_name)( rest, << acc::binary, - unquote(name)(c1)::4, - unquote(name)(c2)::4, - unquote(name)(c3)::4, - unquote(name)(c4)::4 + unquote(decode_name)(c1)::4, + unquote(decode_name)(c2)::4, + unquote(decode_name)(c3)::4, + unquote(decode_name)(c4)::4 >> ) end - defp unquote(name)(<>, acc) do - unquote(name)(rest, <>) + defp unquote(decode_name)(<>, acc) do + unquote(decode_name)( + rest, + <> + ) end - defp unquote(name)(<<>>, acc) do + defp unquote(decode_name)(<<>>, acc) do acc end end @@ -559,6 +626,43 @@ defmodule Base do string |> remove_ignored(opts[:ignore]) |> decode64base!(pad?) end + @doc """ + Validates a base 64 encoded string. + + > #### When to use this {: .tip} + > + > Use this function when you just need to *validate* that a string is + > valid base 64 data, without actually producing a decoded output string. + > This function is both more performant and memory efficient than using + > `decode64/2`, checking that the result is `{:ok, ...}`, and then + > discarding the decoded binary. + + ## Options + + Accepts the same options as `decode64/2`. + + ## Examples + + iex> Base.valid64?("Zm9vYmFy") + true + + iex> Base.valid64?("Zm9vYmFy\\n", ignore: :whitespace) + true + + iex> Base.valid64?("Zm9vYg==") + true + + """ + @doc since: "1.19.0" + @spec valid64?(binary, ignore: :whitespace, padding: boolean) :: boolean + def valid64?(string, opts \\ []) when is_binary(string) do + pad? = Keyword.get(opts, :padding, true) + string |> remove_ignored(opts[:ignore]) |> validate64base!(pad?) + true + rescue + ArgumentError -> false + end + @doc """ Decodes a base 64 encoded string with URL and filename safe alphabet into a binary string. @@ -619,11 +723,142 @@ defmodule Base do string |> remove_ignored(opts[:ignore]) |> decode64url!(pad?) end + @doc """ + Validates a base 64 encoded string with URL and filename safe alphabet. + + > #### When to use this {: .tip} + > + > Use this function when you just need to *validate* that a string is + > valid (URL-safe) base 64 data, without actually producing a decoded + > output string. This function is both more performant and memory efficient + > than using `url_decode64/2`, checking that the result is `{:ok, ...}`, + > and then discarding the decoded binary. + + ## Options + + Accepts the same options as `url_decode64/2`. + + ## Examples + + iex> Base.url_valid64?("_3_-_A==") + true + + iex> Base.url_valid64?("_3_-_A==\\n", ignore: :whitespace) + true + + iex> Base.url_valid64?("_3_-_A", padding: false) + true + + """ + @doc since: "1.19.0" + @spec url_valid64?(binary, ignore: :whitespace, padding: boolean) :: boolean + def url_valid64?(string, opts \\ []) when is_binary(string) do + pad? = Keyword.get(opts, :padding, true) + string |> remove_ignored(opts[:ignore]) |> validate64url!(pad?) + true + rescue + ArgumentError -> false + end + for {base, alphabet} <- [base: b64_alphabet, url: b64url_alphabet] do - name = :"decode64#{base}!" + decode_name = :"decode64#{base}!" + validate_name = :"validate64#{base}!" {min, decoded} = alphabet |> Enum.with_index() |> to_decode_list.() - defp unquote(name)(char) do + defp unquote(validate_name)(<<>>, _pad?) do + true + end + + defp unquote(validate_name)(string, pad?) do + segs = div(byte_size(string) + 7, 8) - 1 + <> = string + + for <> do + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + unquote(decode_name)(c8) + end + + case rest do + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + unquote(decode_name)(c8) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + + _ -> + raise ArgumentError, "incorrect padding" + end + end + + defp unquote(decode_name)(char) do try do elem({unquote_splicing(decoded)}, char - unquote(min)) rescue @@ -634,105 +869,107 @@ defmodule Base do end end - defp unquote(name)(<<>>, _pad?), do: <<>> + defp unquote(decode_name)(<<>>, _pad?), do: <<>> - defp unquote(name)(string, pad?) do + defp unquote(decode_name)(string, pad?) do segs = div(byte_size(string) + 7, 8) - 1 <> = string main = for <>, into: <<>> do << - unquote(name)(c1)::6, - unquote(name)(c2)::6, - unquote(name)(c3)::6, - unquote(name)(c4)::6, - unquote(name)(c5)::6, - unquote(name)(c6)::6, - unquote(name)(c7)::6, - unquote(name)(c8)::6 + unquote(decode_name)(c1)::6, + unquote(decode_name)(c2)::6, + unquote(decode_name)(c3)::6, + unquote(decode_name)(c4)::6, + unquote(decode_name)(c5)::6, + unquote(decode_name)(c6)::6, + unquote(decode_name)(c7)::6, + unquote(decode_name)(c8)::6 >> end case rest do <> -> - <> + <> <> -> - <> + <> <> -> << main::bits, - unquote(name)(c1)::6, - unquote(name)(c2)::6, - unquote(name)(c3)::6, - unquote(name)(c4)::6 + unquote(decode_name)(c1)::6, + unquote(decode_name)(c2)::6, + unquote(decode_name)(c3)::6, + unquote(decode_name)(c4)::6 >> <> -> << main::bits, - unquote(name)(c1)::6, - unquote(name)(c2)::6, - unquote(name)(c3)::6, - unquote(name)(c4)::6, - unquote(name)(c5)::6, - bsr(unquote(name)(c6), 4)::2 + unquote(decode_name)(c1)::6, + unquote(decode_name)(c2)::6, + unquote(decode_name)(c3)::6, + unquote(decode_name)(c4)::6, + unquote(decode_name)(c5)::6, + bsr(unquote(decode_name)(c6), 4)::2 >> <> -> << main::bits, - unquote(name)(c1)::6, - unquote(name)(c2)::6, - unquote(name)(c3)::6, - unquote(name)(c4)::6, - unquote(name)(c5)::6, - unquote(name)(c6)::6, - bsr(unquote(name)(c7), 2)::4 + unquote(decode_name)(c1)::6, + unquote(decode_name)(c2)::6, + unquote(decode_name)(c3)::6, + unquote(decode_name)(c4)::6, + unquote(decode_name)(c5)::6, + unquote(decode_name)(c6)::6, + bsr(unquote(decode_name)(c7), 2)::4 >> <> -> << main::bits, - unquote(name)(c1)::6, - unquote(name)(c2)::6, - unquote(name)(c3)::6, - unquote(name)(c4)::6, - unquote(name)(c5)::6, - unquote(name)(c6)::6, - unquote(name)(c7)::6, - unquote(name)(c8)::6 + unquote(decode_name)(c1)::6, + unquote(decode_name)(c2)::6, + unquote(decode_name)(c3)::6, + unquote(decode_name)(c4)::6, + unquote(decode_name)(c5)::6, + unquote(decode_name)(c6)::6, + unquote(decode_name)(c7)::6, + unquote(decode_name)(c8)::6 >> <> when not pad? -> - <> + <> <> when not pad? -> - <> + <> <> when not pad? -> << main::bits, - unquote(name)(c1)::6, - unquote(name)(c2)::6, - unquote(name)(c3)::6, - unquote(name)(c4)::6, - unquote(name)(c5)::6, - bsr(unquote(name)(c6), 4)::2 + unquote(decode_name)(c1)::6, + unquote(decode_name)(c2)::6, + unquote(decode_name)(c3)::6, + unquote(decode_name)(c4)::6, + unquote(decode_name)(c5)::6, + bsr(unquote(decode_name)(c6), 4)::2 >> <> when not pad? -> << main::bits, - unquote(name)(c1)::6, - unquote(name)(c2)::6, - unquote(name)(c3)::6, - unquote(name)(c4)::6, - unquote(name)(c5)::6, - unquote(name)(c6)::6, - bsr(unquote(name)(c7), 2)::4 + unquote(decode_name)(c1)::6, + unquote(decode_name)(c2)::6, + unquote(decode_name)(c3)::6, + unquote(decode_name)(c4)::6, + unquote(decode_name)(c5)::6, + unquote(decode_name)(c6)::6, + bsr(unquote(decode_name)(c7), 2)::4 >> _ -> @@ -987,6 +1224,49 @@ defmodule Base do end end + @doc """ + Checks if a base 32 encoded string is valid. + + > #### When to use this {: .tip} + > + > Use this function when you just need to *validate* that a string is + > valid base 32 data, without actually producing a decoded output string. + > This function is both more performant and memory efficient than using + > `decode32/2`, checking that the result is `{:ok, ...}`, and then + > discarding the decoded binary. + + ## Options + + Accepts the same options as `decode32/2`. + + ## Examples + + iex> Base.valid32?("MZXW6YTBOI======") + true + + iex> Base.valid32?("mzxw6ytboi======", case: :lower) + true + + iex> Base.valid32?("zzz") + false + + """ + @doc since: "1.19.0" + @spec valid32?(binary, case: decode_case, padding: boolean) :: boolean() + def valid32?(string, opts \\ []) when is_binary(string) do + pad? = Keyword.get(opts, :padding, true) + + case Keyword.get(opts, :case, :upper) do + :upper -> validate32upper!(string, pad?) + :lower -> validate32lower!(string, pad?) + :mixed -> validate32mixed!(string, pad?) + end + + true + rescue + ArgumentError -> false + end + @doc """ Decodes a base 32 encoded string with extended hexadecimal alphabet into a binary string. @@ -1082,6 +1362,49 @@ defmodule Base do end end + @doc """ + Checks if a base 32 encoded string with extended hexadecimal alphabet is valid. + + > #### When to use this {: .tip} + > + > Use this function when you just need to *validate* that a string is + > valid (extended hexadecimal) base 32 data, without actually producing + > a decoded output string. This function is both more performant and + > memory efficient than using `hex_decode32/2`, checking that the result + > is `{:ok, ...}`, and then discarding the decoded binary. + + ## Options + + Accepts the same options as `hex_decode32/2`. + + ## Examples + + iex> Base.hex_valid32?("CPNMUOJ1E8======") + true + + iex> Base.hex_valid32?("cpnmuoj1e8======", case: :lower) + true + + iex> Base.hex_valid32?("zzz", padding: false) + false + + """ + @doc since: "1.19.0" + @spec hex_valid32?(binary, case: decode_case, padding: boolean) :: boolean + def hex_valid32?(string, opts \\ []) when is_binary(string) do + pad? = Keyword.get(opts, :padding, true) + + case Keyword.get(opts, :case, :upper) do + :upper -> validate32hexupper!(string, pad?) + :lower -> validate32hexlower!(string, pad?) + :mixed -> validate32hexmixed!(string, pad?) + end + + true + rescue + ArgumentError -> false + end + upper = Enum.with_index(b32_alphabet) hexupper = Enum.with_index(b32hex_alphabet) @@ -1093,10 +1416,98 @@ defmodule Base do hexlower: to_lower_dec.(hexupper), hexmixed: to_mixed_dec.(hexupper) ] do - name = :"decode32#{base}!" + decode_name = :"decode32#{base}!" + validate_name = :"validate32#{base}!" {min, decoded} = to_decode_list.(alphabet) - defp unquote(name)(char) do + defp unquote(validate_name)(<<>>, _pad?) do + :ok + end + + defp unquote(validate_name)(string, pad?) do + segs = div(byte_size(string) + 7, 8) - 1 + <> = string + + for <> do + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + unquote(decode_name)(c8) + end + + case rest do + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + + <> -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + unquote(decode_name)(c8) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + + <> when not pad? -> + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) + unquote(decode_name)(c6) + unquote(decode_name)(c7) + + _ -> + raise ArgumentError, "incorrect padding" + end + end + + defp unquote(decode_name)(char) do try do elem({unquote_splicing(decoded)}, char - unquote(min)) rescue @@ -1107,106 +1518,106 @@ defmodule Base do end end - defp unquote(name)(<<>>, _), do: <<>> + defp unquote(decode_name)(<<>>, _), do: <<>> - defp unquote(name)(string, pad?) do + defp unquote(decode_name)(string, pad?) do segs = div(byte_size(string) + 7, 8) - 1 <> = string main = for <>, into: <<>> do << - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - unquote(name)(c4)::5, - unquote(name)(c5)::5, - unquote(name)(c6)::5, - unquote(name)(c7)::5, - unquote(name)(c8)::5 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + unquote(decode_name)(c4)::5, + unquote(decode_name)(c5)::5, + unquote(decode_name)(c6)::5, + unquote(decode_name)(c7)::5, + unquote(decode_name)(c8)::5 >> end case rest do <> -> - <> + <> <> -> << main::bits, - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - bsr(unquote(name)(c4), 4)::1 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + bsr(unquote(decode_name)(c4), 4)::1 >> <> -> << main::bits, - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - unquote(name)(c4)::5, - bsr(unquote(name)(c5), 1)::4 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + unquote(decode_name)(c4)::5, + bsr(unquote(decode_name)(c5), 1)::4 >> <> -> << main::bits, - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - unquote(name)(c4)::5, - unquote(name)(c5)::5, - unquote(name)(c6)::5, - bsr(unquote(name)(c7), 3)::2 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + unquote(decode_name)(c4)::5, + unquote(decode_name)(c5)::5, + unquote(decode_name)(c6)::5, + bsr(unquote(decode_name)(c7), 3)::2 >> <> -> << main::bits, - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - unquote(name)(c4)::5, - unquote(name)(c5)::5, - unquote(name)(c6)::5, - unquote(name)(c7)::5, - unquote(name)(c8)::5 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + unquote(decode_name)(c4)::5, + unquote(decode_name)(c5)::5, + unquote(decode_name)(c6)::5, + unquote(decode_name)(c7)::5, + unquote(decode_name)(c8)::5 >> <> when not pad? -> - <> + <> <> when not pad? -> << main::bits, - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - bsr(unquote(name)(c4), 4)::1 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + bsr(unquote(decode_name)(c4), 4)::1 >> <> when not pad? -> << main::bits, - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - unquote(name)(c4)::5, - bsr(unquote(name)(c5), 1)::4 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + unquote(decode_name)(c4)::5, + bsr(unquote(decode_name)(c5), 1)::4 >> <> when not pad? -> << main::bits, - unquote(name)(c1)::5, - unquote(name)(c2)::5, - unquote(name)(c3)::5, - unquote(name)(c4)::5, - unquote(name)(c5)::5, - unquote(name)(c6)::5, - bsr(unquote(name)(c7), 3)::2 + unquote(decode_name)(c1)::5, + unquote(decode_name)(c2)::5, + unquote(decode_name)(c3)::5, + unquote(decode_name)(c4)::5, + unquote(decode_name)(c5)::5, + unquote(decode_name)(c6)::5, + bsr(unquote(decode_name)(c7), 3)::2 >> _ -> diff --git a/lib/elixir/test/elixir/base_test.exs b/lib/elixir/test/elixir/base_test.exs index 62b6647092a..aeb5f4b5c24 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -88,6 +88,30 @@ defmodule BaseTest do end end + test "valid16?/1" do + assert valid16?("") + assert valid16?("66") + assert valid16?("666F") + assert valid16?("666F6F") + assert valid16?("666F6F62") + assert valid16?("666F6F6261") + assert valid16?("666F6F626172") + assert valid16?("A1B2C3D4E5F67891") + assert valid16?("a1b2c3d4e5f67891", case: :lower) + assert valid16?("a1B2c3D4e5F67891", case: :mixed) + end + + test "valid16?/1 returns false on non-alphabet character" do + refute valid16?("66KF") + refute valid16?("66ff") + refute valid16?("66FF", case: :lower) + refute valid16?("66fg", case: :mixed) + end + + test "valid16?/1 errors on odd-length string" do + refute valid16?("666") + end + test "encode64/1 can deal with empty strings" do assert "" == encode64("") end @@ -219,6 +243,52 @@ defmodule BaseTest do assert "Hello World" == decode64!("SGVsbG8gV29ybGQ", padding: false) end + test "valid64?/1 can deal with empty strings" do + assert valid64?("") + end + + test "valid64?/1 with two pads" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") + end + + test "valid64?/1 with one pad" do + assert valid64?("SGVsbG8gV29ybGQ=") + end + + test "valid64?/1 with no pad" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft") + end + + test "valid64?/1 returns false on non-alphabet character" do + refute valid64?("Zm9)") + end + + test "valid64?/1 returns false on whitespace unless there's ignore: :whitespace" do + refute valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") + + assert valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) + end + + test "valid64?/1 returns false on incorrect padding" do + refute valid64?("SGVsbG8gV29ybGQ") + end + + test "valid64?/2 with two pads and ignoring padding" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) + end + + test "valid64?/2 with one pad and ignoring padding" do + assert valid64?("SGVsbG8gV29ybGQ", padding: false) + end + + test "valid64?/2 with no pad and ignoring padding" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) + end + + test "valid64?/2 with incorrect padding and ignoring padding" do + assert valid64?("SGVsbG8gV29ybGQ", padding: false) + end + test "url_encode64/1 can deal with empty strings" do assert "" == url_encode64("") end @@ -356,6 +426,56 @@ defmodule BaseTest do assert "Hello World" == url_decode64!("SGVsbG8gV29ybGQ", padding: false) end + test "url_valid64?/1 can deal with empty strings" do + assert url_valid64?("") + end + + test "url_valid64?/1 with two pads" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") + end + + test "url_valid64?/1 with one pad" do + assert url_valid64?("SGVsbG8gV29ybGQ=") + end + + test "url_valid64?/1 with no pad" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft") + end + + test "url_valid64?/1 returns false on non-alphabet character" do + refute url_valid64?("Zm9)") + end + + test "url_valid64?/1 returns false on whitespace unless there's ignore: :whitespace" do + refute url_valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") + + assert url_valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) + end + + test "url_valid64?/1 returns false on incorrect padding" do + refute url_valid64?("SGVsbG8gV29ybGQ") + end + + test "url_valid64?/2 with two pads and ignoring padding" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) + end + + test "url_valid64?/2 with one pad and ignoring padding" do + assert url_valid64?("SGVsbG8gV29ybGQ", padding: false) + end + + test "url_valid64?/2 with no pad and ignoring padding" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) + end + + test "url_valid64?/2 errors on incorrect padding" do + refute url_valid64?("SGVsbG8gV29ybGQ") + end + + test "url_valid64?/2 ignores incorrect padding when :padding is false" do + assert url_valid64?("SGVsbG8gV29ybGQ", padding: false) + end + test "encode32/1 can deal with empty strings" do assert "" == encode32("") end @@ -569,6 +689,81 @@ defmodule BaseTest do "foob" = decode32!("MZXW6YQ", padding: false) end + test "valid32?/1 can deal with empty strings" do + assert valid32?("") + end + + test "valid32?/1 with one pad" do + assert valid32?("MZXW6YQ=") + end + + test "valid32?/1 with three pads" do + assert valid32?("MZXW6===") + end + + test "valid32?/1 with four pads" do + assert valid32?("MZXQ====") + end + + test "valid32?/1 with lowercase" do + assert valid32?("mzxq====", case: :lower) + end + + test "valid32?/1 with mixed case" do + assert valid32?("mZXq====", case: :mixed) + end + + test "valid32?/1 with six pads" do + assert valid32?("MZXW6YTBOI======") + end + + test "valid32?/1 with no pads" do + assert valid32?("MZXW6YTB") + end + + test "valid32?/1,2 returns false on non-alphabet character" do + refute valid32?("MZX)6YTB") + refute valid32?("66ff") + refute valid32?("66FF", case: :lower) + refute valid32?("0ZXW6YTB0I======", case: :mixed) + end + + test "valid32?/1 returns false on incorrect padding" do + refute valid32?("MZXW6YQ") + end + + test "valid32?/2 with one pad and :padding to false" do + assert valid32?("MZXW6YQ", padding: false) + end + + test "valid32?/2 with three pads and ignoring padding" do + assert valid32?("MZXW6", padding: false) + end + + test "valid32?/2 with four pads and ignoring padding" do + assert valid32?("MZXQ", padding: false) + end + + test "valid32?/2 with :lower case and ignoring padding" do + assert valid32?("mzxq", case: :lower, padding: false) + end + + test "valid32?/2 with :mixed case and ignoring padding" do + assert valid32?("mZXq", case: :mixed, padding: false) + end + + test "valid32?/2 with six pads and ignoring padding" do + assert valid32?("MZXW6YTBOI", padding: false) + end + + test "valid32?/2 with no pads and ignoring padding" do + assert valid32?("MZXW6YTB", padding: false) + end + + test "valid32?/2 ignores incorrect padding when :padding is false" do + assert valid32?("MZXW6YQ", padding: false) + end + test "hex_encode32/1 can deal with empty strings" do assert "" == hex_encode32("") end @@ -784,13 +979,89 @@ defmodule BaseTest do assert "fo" == hex_decode32!("cPNg", case: :mixed, padding: false) end + test "hex_valid32?/1 can deal with empty strings" do + assert hex_valid32?("") + end + + test "hex_valid32?/1 with one pad" do + assert hex_valid32?("CPNMUOG=") + end + + test "hex_valid32?/1 with three pads" do + assert hex_valid32?("CPNMU===") + end + + test "hex_valid32?/1 with four pads" do + assert hex_valid32?("CPNG====") + end + + test "hex_valid32?/1 with six pads" do + assert hex_valid32?("CPNMUOJ1E8======") + assert hex_valid32?("CO======") + end + + test "hex_valid32?/1 with no pads" do + assert hex_valid32?("CPNMUOJ1") + end + + test "hex_valid32?/1,2 returns false on non-alphabet character" do + refute hex_valid32?("CPN)UOJ1") + refute hex_valid32?("66f") + refute hex_valid32?("66F", case: :lower) + end + + test "hex_valid32?/1 returns false on incorrect padding" do + refute hex_valid32?("CPNMUOG") + end + + test "hex_valid32?/2 with lowercase" do + assert hex_valid32?("cpng====", case: :lower) + end + + test "hex_valid32?/2 with mixed case" do + assert hex_valid32?("cPNg====", case: :mixed) + end + + test "hex_valid32?/2 with one pad and ignoring padding" do + assert hex_valid32?("CPNMUOG", padding: false) + end + + test "hex_valid32?/2 with three pads and ignoring padding" do + assert hex_valid32?("CPNMU", padding: false) + end + + test "hex_valid32?/2 with four pads and ignoring padding" do + assert hex_valid32?("CPNG", padding: false) + end + + test "hex_valid32?/2 with six pads and ignoring padding" do + assert hex_valid32?("CPNMUOJ1E8", padding: false) + end + + test "hex_valid32?/2 with no pads and ignoring padding" do + assert hex_valid32?("CPNMUOJ1", padding: false) + end + + test "hex_valid32?/2 ignores incorrect padding when :padding is false" do + assert hex_valid32?("CPNMUOG", padding: false) + end + + test "hex_valid32?/2 with :lower case and ignoring padding" do + assert hex_valid32?("cpng", case: :lower, padding: false) + end + + test "hex_valid32?/2 with :mixed case and ignoring padding" do + assert hex_valid32?("cPNg====", case: :mixed, padding: false) + end + + # TODO: add valid? tests test "encode then decode is identity" do - for {encode, decode} <- [ - {&encode16/2, &decode16!/2}, - {&encode32/2, &decode32!/2}, - {&hex_encode32/2, &hex_decode32!/2}, - {&encode64/2, &decode64!/2}, - {&url_encode64/2, &url_decode64!/2} + for {encode, decode, valid?} <- [ + {&encode16/2, &decode16!/2, &valid16?/2}, + {&encode32/2, &decode32!/2, &valid32?/2}, + {&hex_encode32/2, &hex_decode32!/2, &hex_valid32?/2}, + {&encode64/2, &decode64!/2, &valid64?/2}, + {&url_encode64/2, &url_decode64!/2, &url_valid64?/2} ], encode_case <- [:upper, :lower], decode_case <- [:upper, :lower, :mixed], @@ -814,10 +1085,11 @@ defmodule BaseTest do _ -> [:case, :padding] end - expected = - data - |> encode.(Keyword.take([case: encode_case, padding: pad?], allowed_opts)) - |> decode.(Keyword.take([case: decode_case, padding: pad?], allowed_opts)) + encoded = encode.(data, Keyword.take([case: encode_case, padding: pad?], allowed_opts)) + + decode_opts = Keyword.take([case: decode_case, padding: pad?], allowed_opts) + assert valid?.(encoded, decode_opts) + expected = decode.(encoded, decode_opts) assert data == expected, "identity did not match for #{inspect(data)} when #{inspect(encode)} (#{encode_case})"