From 746432e91e5577d3f9045dd0fafa04c5c4dd0a53 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 11:29:19 +0200 Subject: [PATCH 01/13] Add Base.valid16?/1 --- lib/elixir/lib/base.ex | 116 ++++++++++++++++++++++----- lib/elixir/test/elixir/base_test.exs | 27 +++++++ 2 files changed, 122 insertions(+), 21 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index a9d4f542c2a..41f251c4aac 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -319,13 +319,84 @@ 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. + + Use this function when you just need to *validate* that a string is valid base64 data, + without actually producing a decoded output string. + + ## Options + + The accepted options are: + + * `:case` - specifies the character case to accept when decoding + + The values for `:case` can be: + + * `:upper` - only allows upper case characters (default) + * `:lower` - only allows lower case characters + * `:mixed` - allows mixed case characters + + An `ArgumentError` exception is raised if the padding is incorrect or + a non-alphabet character is present in the string. + + ## 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 + valid_chars = Enum.map(alphabet, fn {char, _val} -> char end) + + defp unquote(validate_name)(<<>>), do: :ok + + defp unquote(validate_name)(<>) + when c1 in unquote(valid_chars) and c2 in unquote(valid_chars) do + 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 +407,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 diff --git a/lib/elixir/test/elixir/base_test.exs b/lib/elixir/test/elixir/base_test.exs index 62b6647092a..a6625cdff47 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -88,6 +88,33 @@ 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) + end + + test "valid16?/1 errors on odd-length string" do + refute valid16?("666") + end + + test "valid16?/1 errors odd-length string" do + refute valid16?("666") + end + test "encode64/1 can deal with empty strings" do assert "" == encode64("") end From fc86bc3e36fa62d6612a121ddf929e1b3ad3bce2 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 12:28:04 +0200 Subject: [PATCH 02/13] base32 --- lib/elixir/lib/base.ex | 267 +++++++++++++++++++++------ lib/elixir/test/elixir/base_test.exs | 161 +++++++++++++++- 2 files changed, 370 insertions(+), 58 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 41f251c4aac..a31868e7f0a 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -1061,6 +1061,39 @@ defmodule Base do end end + @doc """ + Checks if a base 32 encoded string is valid. + + See `decode32/2` for the accepted options. + + ## 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. @@ -1156,6 +1189,39 @@ defmodule Base do end end + @doc """ + Checks if a base 32 encoded string with extended hexadecimal alphabet is valid. + + See `decode32/2` for the accepted options. + + ## 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) @@ -1167,10 +1233,103 @@ 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 + valid_chars = Enum.map(alphabet, fn {char, _val} -> char end) + + defp unquote(validate_name)(char) when char in unquote(valid_chars), do: :ok + defp unquote(validate_name)(char), do: bad_character!(char) + + 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(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + unquote(validate_name)(c8) + end + + case rest do + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + unquote(validate_name)(c8) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + + _ -> + raise ArgumentError, "incorrect padding" + end + end + + defp unquote(decode_name)(char) do try do elem({unquote_splicing(decoded)}, char - unquote(min)) rescue @@ -1181,106 +1340,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 a6625cdff47..748f0b97611 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -111,10 +111,6 @@ defmodule BaseTest do refute valid16?("666") end - test "valid16?/1 errors odd-length string" do - refute valid16?("666") - end - test "encode64/1 can deal with empty strings" do assert "" == encode64("") end @@ -596,6 +592,87 @@ 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 @@ -811,6 +888,82 @@ 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}, From 243266f46f12403f009227deb78847296914d5ae Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 12:34:05 +0200 Subject: [PATCH 03/13] base32 --- lib/elixir/lib/base.ex | 223 +++++++++++++++++++++++++++++++---------- 1 file changed, 170 insertions(+), 53 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index a31868e7f0a..23b7b9c664d 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -633,6 +633,14 @@ defmodule Base do string |> remove_ignored(opts[:ignore]) |> decode64base!(pad?) end + 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. @@ -693,11 +701,118 @@ defmodule Base do string |> remove_ignored(opts[:ignore]) |> decode64url!(pad?) end + 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}!" + validate_name = :"validate64#{base}!" {min, decoded} = alphabet |> Enum.with_index() |> to_decode_list.() - defp unquote(name)(char) do + valid_chars = Enum.map(decoded, fn {char, _} -> char end) + + defp unquote(validate_name)(char) when char in unquote(valid_chars), do: char + defp unquote(validate_name)(char), do: bad_character!(char) + + 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(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + unquote(validate_name)(c8) + end + + case rest do + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + + <> -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + unquote(validate_name)(c8) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + + <> when not pad? -> + unquote(validate_name)(c1) + unquote(validate_name)(c2) + unquote(validate_name)(c3) + unquote(validate_name)(c4) + unquote(validate_name)(c5) + unquote(validate_name)(c6) + unquote(validate_name)(c7) + + _ -> + raise ArgumentError, "incorrect padding" + end + end + + defp unquote(decode_name)(char) do try do elem({unquote_splicing(decoded)}, char - unquote(min)) rescue @@ -708,105 +823,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 >> _ -> From 48b372227bb0977cdd9c73720c31d52219741650 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 12:38:09 +0200 Subject: [PATCH 04/13] base64 --- lib/elixir/lib/base.ex | 6 ++---- lib/elixir/test/elixir/base_test.exs | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 23b7b9c664d..4d64efaeb36 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -710,13 +710,11 @@ defmodule Base do 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.() - valid_chars = Enum.map(decoded, fn {char, _} -> char end) - - defp unquote(validate_name)(char) when char in unquote(valid_chars), do: char + defp unquote(validate_name)(char) when char in unquote(alphabet), do: char defp unquote(validate_name)(char), do: bad_character!(char) defp unquote(validate_name)(<<>>, _pad?) do diff --git a/lib/elixir/test/elixir/base_test.exs b/lib/elixir/test/elixir/base_test.exs index 748f0b97611..000ff87ad47 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -965,12 +965,12 @@ defmodule BaseTest do # 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], @@ -994,10 +994,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})" From 0c1dc8dc82ec2a0e2322101aaa3bdfa0fcf6a5d8 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 12:39:02 +0200 Subject: [PATCH 05/13] base64 --- lib/elixir/lib/base.ex | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 4d64efaeb36..e7724df043e 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -633,6 +633,27 @@ defmodule Base do string |> remove_ignored(opts[:ignore]) |> decode64base!(pad?) end + @doc """ + Validates a base 64 encoded string. + + ## Options + + Same 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?) From fd281c496e0c22611b77d026b0a054eb3d928252 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 12:39:50 +0200 Subject: [PATCH 06/13] base64 --- lib/elixir/lib/base.ex | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index e7724df043e..b86dfc935b2 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -722,6 +722,27 @@ defmodule Base do string |> remove_ignored(opts[:ignore]) |> decode64url!(pad?) end + @doc """ + Validates a base 64 encoded string with URL and filename safe alphabet. + + ## Options + + Same 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?) From cf2fa1dec4a562baffde1542ca023ba3ae3fa88d Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 12:52:48 +0200 Subject: [PATCH 07/13] base64 tests --- lib/elixir/lib/base.ex | 56 ++--- lib/elixir/test/elixir/base_test.exs | 296 ++++++++++++++++++--------- 2 files changed, 224 insertions(+), 128 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index b86dfc935b2..52ede7bd093 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -342,24 +342,24 @@ defmodule Base do ## Examples - iex> Base.valid16?("666F6F626172") + iex> Base.valid_decode16?("666F6F626172") true - iex> Base.valid16?("666f6f626172", case: :lower) + iex> Base.valid_decode16?("666f6f626172", case: :lower) true - iex> Base.valid16?("666f6F626172", case: :mixed) + iex> Base.valid_decode16?("666f6F626172", case: :mixed) true - iex> Base.valid16?("ff", case: :upper) + iex> Base.valid_decode16?("ff", case: :upper) false """ @doc since: "1.19.0" - @spec valid16?(binary, case: decode_case) :: boolean - def valid16?(string, opts \\ []) + @spec valid_decode16?(binary, case: decode_case) :: boolean + def valid_decode16?(string, opts \\ []) - def valid16?(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do + def valid_decode16?(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) @@ -371,7 +371,7 @@ defmodule Base do ArgumentError -> false end - def valid16?(string, _opts) when is_binary(string) do + def valid_decode16?(string, _opts) when is_binary(string) do false end @@ -642,19 +642,19 @@ defmodule Base do ## Examples - iex> Base.valid64?("Zm9vYmFy") + iex> Base.valid_decode64?("Zm9vYmFy") true - iex> Base.valid64?("Zm9vYmFy\\n", ignore: :whitespace) + iex> Base.valid_decode64?("Zm9vYmFy\\n", ignore: :whitespace) true - iex> Base.valid64?("Zm9vYg==") + iex> Base.valid_decode64?("Zm9vYg==") true """ @doc since: "1.19.0" - @spec valid64?(binary, ignore: :whitespace, padding: boolean) :: boolean - def valid64?(string, opts \\ []) when is_binary(string) do + @spec valid_decode64?(binary, ignore: :whitespace, padding: boolean) :: boolean + def valid_decode64?(string, opts \\ []) when is_binary(string) do pad? = Keyword.get(opts, :padding, true) string |> remove_ignored(opts[:ignore]) |> validate64base!(pad?) true @@ -731,19 +731,19 @@ defmodule Base do ## Examples - iex> Base.url_valid64?("_3_-_A==") + iex> Base.valid_url_decode64?("_3_-_A==") true - iex> Base.url_valid64?("_3_-_A==\\n", ignore: :whitespace) + iex> Base.valid_url_decode64?("_3_-_A==\\n", ignore: :whitespace) true - iex> Base.url_valid64?("_3_-_A", padding: false) + iex> Base.valid_url_decode64?("_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 + @spec valid_url_decode64?(binary, ignore: :whitespace, padding: boolean) :: boolean + def valid_url_decode64?(string, opts \\ []) when is_binary(string) do pad? = Keyword.get(opts, :padding, true) string |> remove_ignored(opts[:ignore]) |> validate64url!(pad?) true @@ -1225,19 +1225,19 @@ defmodule Base do ## Examples - iex> Base.valid32?("MZXW6YTBOI======") + iex> Base.valid_decode32?("MZXW6YTBOI======") true - iex> Base.valid32?("mzxw6ytboi======", case: :lower) + iex> Base.valid_decode32?("mzxw6ytboi======", case: :lower) true - iex> Base.valid32?("zzz") + iex> Base.valid_decode32?("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 + @spec valid_decode32?(binary, case: decode_case, padding: boolean) :: boolean() + def valid_decode32?(string, opts \\ []) when is_binary(string) do pad? = Keyword.get(opts, :padding, true) case Keyword.get(opts, :case, :upper) do @@ -1353,19 +1353,19 @@ defmodule Base do ## Examples - iex> Base.hex_valid32?("CPNMUOJ1E8======") + iex> Base.valid_hex_decode32?("CPNMUOJ1E8======") true - iex> Base.hex_valid32?("cpnmuoj1e8======", case: :lower) + iex> Base.valid_hex_decode32?("cpnmuoj1e8======", case: :lower) true - iex> Base.hex_valid32?("zzz", padding: false) + iex> Base.valid_hex_decode32?("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 + @spec valid_hex_decode32?(binary, case: decode_case, padding: boolean) :: boolean + def valid_hex_decode32?(string, opts \\ []) when is_binary(string) do pad? = Keyword.get(opts, :padding, true) case Keyword.get(opts, :case, :upper) do diff --git a/lib/elixir/test/elixir/base_test.exs b/lib/elixir/test/elixir/base_test.exs index 000ff87ad47..53057d5e23c 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -88,27 +88,27 @@ 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) + test "valid_decode16?/1" do + assert valid_decode16?("") + assert valid_decode16?("66") + assert valid_decode16?("666F") + assert valid_decode16?("666F6F") + assert valid_decode16?("666F6F62") + assert valid_decode16?("666F6F6261") + assert valid_decode16?("666F6F626172") + assert valid_decode16?("A1B2C3D4E5F67891") + assert valid_decode16?("a1b2c3d4e5f67891", case: :lower) + assert valid_decode16?("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) + test "valid_decode16?/1 returns false on non-alphabet character" do + refute valid_decode16?("66KF") + refute valid_decode16?("66ff") + refute valid_decode16?("66FF", case: :lower) end - test "valid16?/1 errors on odd-length string" do - refute valid16?("666") + test "valid_decode16?/1 errors on odd-length string" do + refute valid_decode16?("666") end test "encode64/1 can deal with empty strings" do @@ -242,6 +242,52 @@ defmodule BaseTest do assert "Hello World" == decode64!("SGVsbG8gV29ybGQ", padding: false) end + test "valid_decode64?/1 can deal with empty strings" do + assert valid_decode64?("") + end + + test "valid_decode64?/1 with two pads" do + assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") + end + + test "valid_decode64?/1 with one pad" do + assert valid_decode64?("SGVsbG8gV29ybGQ=") + end + + test "valid_decode64?/1 with no pad" do + assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft") + end + + test "valid_decode64?/1 returns false on non-alphabet character" do + refute valid_decode64?("Zm9)") + end + + test "valid_decode64?/1 returns false on whitespace unless there's ignore: :whitespace" do + refute valid_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") + + assert valid_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) + end + + test "valid_decode64?/1 returns false on incorrect padding" do + refute valid_decode64?("SGVsbG8gV29ybGQ") + end + + test "valid_decode64?/2 with two pads and ignoring padding" do + assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) + end + + test "valid_decode64?/2 with one pad and ignoring padding" do + assert valid_decode64?("SGVsbG8gV29ybGQ", padding: false) + end + + test "valid_decode64?/2 with no pad and ignoring padding" do + assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) + end + + test "valid_decode64?/2 with incorrect padding and ignoring padding" do + assert valid_decode64?("SGVsbG8gV29ybGQ", padding: false) + end + test "url_encode64/1 can deal with empty strings" do assert "" == url_encode64("") end @@ -379,6 +425,56 @@ defmodule BaseTest do assert "Hello World" == url_decode64!("SGVsbG8gV29ybGQ", padding: false) end + test "valid_url_decode64?/1 can deal with empty strings" do + assert valid_url_decode64?("") + end + + test "valid_url_decode64?/1 with two pads" do + assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") + end + + test "valid_url_decode64?/1 with one pad" do + assert valid_url_decode64?("SGVsbG8gV29ybGQ=") + end + + test "valid_url_decode64?/1 with no pad" do + assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft") + end + + test "valid_url_decode64?/1 returns false on non-alphabet character" do + refute valid_url_decode64?("Zm9)") + end + + test "valid_url_decode64?/1 returns false on whitespace unless there's ignore: :whitespace" do + refute valid_url_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") + + assert valid_url_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) + end + + test "valid_url_decode64?/1 returns false on incorrect padding" do + refute valid_url_decode64?("SGVsbG8gV29ybGQ") + end + + test "valid_url_decode64?/2 with two pads and ignoring padding" do + assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) + end + + test "valid_url_decode64?/2 with one pad and ignoring padding" do + assert valid_url_decode64?("SGVsbG8gV29ybGQ", padding: false) + end + + test "valid_url_decode64?/2 with no pad and ignoring padding" do + assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) + end + + test "valid_url_decode64?/2 errors on incorrect padding" do + refute valid_url_decode64?("SGVsbG8gV29ybGQ") + end + + test "valid_url_decode64?/2 ignores incorrect padding when :padding is false" do + assert valid_url_decode64?("SGVsbG8gV29ybGQ", padding: false) + end + test "encode32/1 can deal with empty strings" do assert "" == encode32("") end @@ -598,79 +694,79 @@ defmodule BaseTest do # # # - test "valid32?/1 can deal with empty strings" do - assert valid32?("") + test "valid_decode32?/1 can deal with empty strings" do + assert valid_decode32?("") end - test "valid32?/1 with one pad" do - assert valid32?("MZXW6YQ=") + test "valid_decode32?/1 with one pad" do + assert valid_decode32?("MZXW6YQ=") end - test "valid32?/1 with three pads" do - assert valid32?("MZXW6===") + test "valid_decode32?/1 with three pads" do + assert valid_decode32?("MZXW6===") end - test "valid32?/1 with four pads" do - assert valid32?("MZXQ====") + test "valid_decode32?/1 with four pads" do + assert valid_decode32?("MZXQ====") end - test "valid32?/1 with lowercase" do - assert valid32?("mzxq====", case: :lower) + test "valid_decode32?/1 with lowercase" do + assert valid_decode32?("mzxq====", case: :lower) end - test "valid32?/1 with mixed case" do - assert valid32?("mZXq====", case: :mixed) + test "valid_decode32?/1 with mixed case" do + assert valid_decode32?("mZXq====", case: :mixed) end - test "valid32?/1 with six pads" do - assert valid32?("MZXW6YTBOI======") + test "valid_decode32?/1 with six pads" do + assert valid_decode32?("MZXW6YTBOI======") end - test "valid32?/1 with no pads" do - assert valid32?("MZXW6YTB") + test "valid_decode32?/1 with no pads" do + assert valid_decode32?("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) + test "valid_decode32?/1,2 returns false on non-alphabet character" do + refute valid_decode32?("MZX)6YTB") + refute valid_decode32?("66ff") + refute valid_decode32?("66FF", case: :lower) + refute valid_decode32?("0ZXW6YTB0I======", case: :mixed) end - test "valid32?/1 returns false on incorrect padding" do - refute valid32?("MZXW6YQ") + test "valid_decode32?/1 returns false on incorrect padding" do + refute valid_decode32?("MZXW6YQ") end - test "valid32?/2 with one pad and :padding to false" do - assert valid32?("MZXW6YQ", padding: false) + test "valid_decode32?/2 with one pad and :padding to false" do + assert valid_decode32?("MZXW6YQ", padding: false) end - test "valid32?/2 with three pads and ignoring padding" do - assert valid32?("MZXW6", padding: false) + test "valid_decode32?/2 with three pads and ignoring padding" do + assert valid_decode32?("MZXW6", padding: false) end - test "valid32?/2 with four pads and ignoring padding" do - assert valid32?("MZXQ", padding: false) + test "valid_decode32?/2 with four pads and ignoring padding" do + assert valid_decode32?("MZXQ", padding: false) end - test "valid32?/2 with :lower case and ignoring padding" do - assert valid32?("mzxq", case: :lower, padding: false) + test "valid_decode32?/2 with :lower case and ignoring padding" do + assert valid_decode32?("mzxq", case: :lower, padding: false) end - test "valid32?/2 with :mixed case and ignoring padding" do - assert valid32?("mZXq", case: :mixed, padding: false) + test "valid_decode32?/2 with :mixed case and ignoring padding" do + assert valid_decode32?("mZXq", case: :mixed, padding: false) end - test "valid32?/2 with six pads and ignoring padding" do - assert valid32?("MZXW6YTBOI", padding: false) + test "valid_decode32?/2 with six pads and ignoring padding" do + assert valid_decode32?("MZXW6YTBOI", padding: false) end - test "valid32?/2 with no pads and ignoring padding" do - assert valid32?("MZXW6YTB", padding: false) + test "valid_decode32?/2 with no pads and ignoring padding" do + assert valid_decode32?("MZXW6YTB", padding: false) end - test "valid32?/2 ignores incorrect padding when :padding is false" do - assert valid32?("MZXW6YQ", padding: false) + test "valid_decode32?/2 ignores incorrect padding when :padding is false" do + assert valid_decode32?("MZXW6YQ", padding: false) end test "hex_encode32/1 can deal with empty strings" do @@ -888,89 +984,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?("") + test "valid_hex_decode32?/1 can deal with empty strings" do + assert valid_hex_decode32?("") end - test "hex_valid32?/1 with one pad" do - assert hex_valid32?("CPNMUOG=") + test "valid_hex_decode32?/1 with one pad" do + assert valid_hex_decode32?("CPNMUOG=") end - test "hex_valid32?/1 with three pads" do - assert hex_valid32?("CPNMU===") + test "valid_hex_decode32?/1 with three pads" do + assert valid_hex_decode32?("CPNMU===") end - test "hex_valid32?/1 with four pads" do - assert hex_valid32?("CPNG====") + test "valid_hex_decode32?/1 with four pads" do + assert valid_hex_decode32?("CPNG====") end - test "hex_valid32?/1 with six pads" do - assert hex_valid32?("CPNMUOJ1E8======") - assert hex_valid32?("CO======") + test "valid_hex_decode32?/1 with six pads" do + assert valid_hex_decode32?("CPNMUOJ1E8======") + assert valid_hex_decode32?("CO======") end - test "hex_valid32?/1 with no pads" do - assert hex_valid32?("CPNMUOJ1") + test "valid_hex_decode32?/1 with no pads" do + assert valid_hex_decode32?("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) + test "valid_hex_decode32?/1,2 returns false on non-alphabet character" do + refute valid_hex_decode32?("CPN)UOJ1") + refute valid_hex_decode32?("66f") + refute valid_hex_decode32?("66F", case: :lower) end - test "hex_valid32?/1 returns false on incorrect padding" do - refute hex_valid32?("CPNMUOG") + test "valid_hex_decode32?/1 returns false on incorrect padding" do + refute valid_hex_decode32?("CPNMUOG") end - test "hex_valid32?/2 with lowercase" do - assert hex_valid32?("cpng====", case: :lower) + test "valid_hex_decode32?/2 with lowercase" do + assert valid_hex_decode32?("cpng====", case: :lower) end - test "hex_valid32?/2 with mixed case" do - assert hex_valid32?("cPNg====", case: :mixed) + test "valid_hex_decode32?/2 with mixed case" do + assert valid_hex_decode32?("cPNg====", case: :mixed) end - test "hex_valid32?/2 with one pad and ignoring padding" do - assert hex_valid32?("CPNMUOG", padding: false) + test "valid_hex_decode32?/2 with one pad and ignoring padding" do + assert valid_hex_decode32?("CPNMUOG", padding: false) end - test "hex_valid32?/2 with three pads and ignoring padding" do - assert hex_valid32?("CPNMU", padding: false) + test "valid_hex_decode32?/2 with three pads and ignoring padding" do + assert valid_hex_decode32?("CPNMU", padding: false) end - test "hex_valid32?/2 with four pads and ignoring padding" do - assert hex_valid32?("CPNG", padding: false) + test "valid_hex_decode32?/2 with four pads and ignoring padding" do + assert valid_hex_decode32?("CPNG", padding: false) end - test "hex_valid32?/2 with six pads and ignoring padding" do - assert hex_valid32?("CPNMUOJ1E8", padding: false) + test "valid_hex_decode32?/2 with six pads and ignoring padding" do + assert valid_hex_decode32?("CPNMUOJ1E8", padding: false) end - test "hex_valid32?/2 with no pads and ignoring padding" do - assert hex_valid32?("CPNMUOJ1", padding: false) + test "valid_hex_decode32?/2 with no pads and ignoring padding" do + assert valid_hex_decode32?("CPNMUOJ1", padding: false) end - test "hex_valid32?/2 ignores incorrect padding when :padding is false" do - assert hex_valid32?("CPNMUOG", padding: false) + test "valid_hex_decode32?/2 ignores incorrect padding when :padding is false" do + assert valid_hex_decode32?("CPNMUOG", padding: false) end - test "hex_valid32?/2 with :lower case and ignoring padding" do - assert hex_valid32?("cpng", case: :lower, padding: false) + test "valid_hex_decode32?/2 with :lower case and ignoring padding" do + assert valid_hex_decode32?("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) + test "valid_hex_decode32?/2 with :mixed case and ignoring padding" do + assert valid_hex_decode32?("cPNg====", case: :mixed, padding: false) end # TODO: add valid? tests test "encode then decode is identity" do 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} + {&encode16/2, &decode16!/2, &valid_decode16?/2}, + {&encode32/2, &decode32!/2, &valid_decode32?/2}, + {&hex_encode32/2, &hex_decode32!/2, &valid_hex_decode32?/2}, + {&encode64/2, &decode64!/2, &valid_decode64?/2}, + {&url_encode64/2, &url_decode64!/2, &valid_url_decode64?/2} ], encode_case <- [:upper, :lower], decode_case <- [:upper, :lower, :mixed], From 9f1102d858ed5a3d79f08b12664776eb1d3c1b57 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 13:52:12 +0200 Subject: [PATCH 08/13] FIXUP --- lib/elixir/lib/base.ex | 224 +++++++++++++-------------- lib/elixir/test/elixir/base_test.exs | 6 - 2 files changed, 108 insertions(+), 122 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 52ede7bd093..49e099a6166 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -756,9 +756,6 @@ defmodule Base do validate_name = :"validate64#{base}!" {min, decoded} = alphabet |> Enum.with_index() |> to_decode_list.() - defp unquote(validate_name)(char) when char in unquote(alphabet), do: char - defp unquote(validate_name)(char), do: bad_character!(char) - defp unquote(validate_name)(<<>>, _pad?) do true end @@ -768,84 +765,84 @@ defmodule Base do <> = string for <> do - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_name)(c7) - unquote(validate_name)(c8) + 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(validate_name)(c1) - unquote(validate_name)(c2) + unquote(decode_name)(c1) + unquote(decode_name)(c2) <> -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) <> -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) <> -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_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(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_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(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_name)(c7) - unquote(validate_name)(c8) + 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(validate_name)(c1) - unquote(validate_name)(c2) + unquote(decode_name)(c1) + unquote(decode_name)(c2) <> when not pad? -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) <> when not pad? -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_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) <> when not pad? -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_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) _ -> raise ArgumentError, "incorrect padding" @@ -1394,11 +1391,6 @@ defmodule Base do validate_name = :"validate32#{base}!" {min, decoded} = to_decode_list.(alphabet) - valid_chars = Enum.map(alphabet, fn {char, _val} -> char end) - - defp unquote(validate_name)(char) when char in unquote(valid_chars), do: :ok - defp unquote(validate_name)(char), do: bad_character!(char) - defp unquote(validate_name)(<<>>, _pad?) do :ok end @@ -1408,78 +1400,78 @@ defmodule Base do <> = string for <> do - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_name)(c7) - unquote(validate_name)(c8) + 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(validate_name)(c1) - unquote(validate_name)(c2) + unquote(decode_name)(c1) + unquote(decode_name)(c2) <> -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) <> -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) <> -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_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(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_name)(c7) - unquote(validate_name)(c8) + 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(validate_name)(c1) - unquote(validate_name)(c2) + unquote(decode_name)(c1) + unquote(decode_name)(c2) <> when not pad? -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) <> when not pad? -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) + unquote(decode_name)(c1) + unquote(decode_name)(c2) + unquote(decode_name)(c3) + unquote(decode_name)(c4) + unquote(decode_name)(c5) <> when not pad? -> - unquote(validate_name)(c1) - unquote(validate_name)(c2) - unquote(validate_name)(c3) - unquote(validate_name)(c4) - unquote(validate_name)(c5) - unquote(validate_name)(c6) - unquote(validate_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) _ -> raise ArgumentError, "incorrect padding" diff --git a/lib/elixir/test/elixir/base_test.exs b/lib/elixir/test/elixir/base_test.exs index 53057d5e23c..118d4a12f86 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -688,12 +688,6 @@ defmodule BaseTest do "foob" = decode32!("MZXW6YQ", padding: false) end - ## - # - # - # - # - # test "valid_decode32?/1 can deal with empty strings" do assert valid_decode32?("") end From 26c838140659bfbb895c1e799a424ac2e8139837 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 21:55:56 +0200 Subject: [PATCH 09/13] FIXUP --- lib/elixir/lib/base.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 49e099a6166..57c4924bc34 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -387,9 +387,9 @@ defmodule Base do defp unquote(validate_name)(<<>>), do: :ok - defp unquote(validate_name)(<>) - when c1 in unquote(valid_chars) and c2 in unquote(valid_chars) do - unquote(validate_name)(rest) + defp unquote(validate_name)(<>) do + unquote(decode_name)(c1) + unquote(decode_name)(c2) end defp unquote(validate_name)(<>) do From 50cb58f40d0d2f40330f04d9ba1639f3dccedecd Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Wed, 9 Apr 2025 22:05:25 +0200 Subject: [PATCH 10/13] FIXUP --- lib/elixir/lib/base.ex | 3 +-- lib/elixir/test/elixir/base_test.exs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 57c4924bc34..1e6ee100b8e 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -383,13 +383,12 @@ defmodule Base do {min, decoded} = to_decode_list.(alphabet) - valid_chars = Enum.map(alphabet, fn {char, _val} -> char end) - 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 diff --git a/lib/elixir/test/elixir/base_test.exs b/lib/elixir/test/elixir/base_test.exs index 118d4a12f86..2021e4b66eb 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -105,6 +105,7 @@ defmodule BaseTest do refute valid_decode16?("66KF") refute valid_decode16?("66ff") refute valid_decode16?("66FF", case: :lower) + refute valid_decode16?("66fg", case: :mixed) end test "valid_decode16?/1 errors on odd-length string" do From f45bdd49b8cfb69b9b9fd3846268c254cf3d4235 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Thu, 10 Apr 2025 12:58:21 +0200 Subject: [PATCH 11/13] Better docs --- lib/elixir/lib/base.ex | 66 ++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 18 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 1e6ee100b8e..2c2fed886ad 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -322,23 +322,17 @@ defmodule Base do @doc """ Checks if a string is a valid base 16 encoded string. - Use this function when you just need to *validate* that a string is valid base64 data, - without actually producing a decoded output 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 - The accepted options are: - - * `:case` - specifies the character case to accept when decoding - - The values for `:case` can be: - - * `:upper` - only allows upper case characters (default) - * `:lower` - only allows lower case characters - * `:mixed` - allows mixed case characters - - An `ArgumentError` exception is raised if the padding is incorrect or - a non-alphabet character is present in the string. + Accepts the same options as `decode16/2`. ## Examples @@ -635,9 +629,17 @@ defmodule Base do @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 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 - Same as `decode64/2`. + Accepts the same options as `decode64/2`. ## Examples @@ -724,9 +726,17 @@ defmodule Base do @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 - Same as `url_decode64/2`. + Accepts the same options as `url_decode64/2`. ## Examples @@ -1217,7 +1227,17 @@ defmodule Base do @doc """ Checks if a base 32 encoded string is valid. - See `decode32/2` for the accepted options. + > #### 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 @@ -1345,7 +1365,17 @@ defmodule Base do @doc """ Checks if a base 32 encoded string with extended hexadecimal alphabet is valid. - See `decode32/2` for the accepted options. + > #### 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 From 0e9e6fe9ed13500f189bcd21acb172e72dd48c7b Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Thu, 10 Apr 2025 15:06:13 +0200 Subject: [PATCH 12/13] Update lib/elixir/lib/base.ex Co-authored-by: Jean Klingler --- lib/elixir/lib/base.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 2c2fed886ad..2ac99a66b1e 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -632,9 +632,9 @@ defmodule Base do > #### 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. + > valid base 64 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 + > `decode64/2`, checking that the result is `{:ok, ...}`, and then > discarding the decoded binary. ## Options From 80b700f018f1a04d0e8cd123098142a37150e754 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Fri, 11 Apr 2025 08:48:25 +0200 Subject: [PATCH 13/13] FIXUP --- lib/elixir/lib/base.ex | 56 ++--- lib/elixir/test/elixir/base_test.exs | 298 +++++++++++++-------------- 2 files changed, 177 insertions(+), 177 deletions(-) diff --git a/lib/elixir/lib/base.ex b/lib/elixir/lib/base.ex index 2ac99a66b1e..6615b5b6743 100644 --- a/lib/elixir/lib/base.ex +++ b/lib/elixir/lib/base.ex @@ -336,24 +336,24 @@ defmodule Base do ## Examples - iex> Base.valid_decode16?("666F6F626172") + iex> Base.valid16?("666F6F626172") true - iex> Base.valid_decode16?("666f6f626172", case: :lower) + iex> Base.valid16?("666f6f626172", case: :lower) true - iex> Base.valid_decode16?("666f6F626172", case: :mixed) + iex> Base.valid16?("666f6F626172", case: :mixed) true - iex> Base.valid_decode16?("ff", case: :upper) + iex> Base.valid16?("ff", case: :upper) false """ @doc since: "1.19.0" - @spec valid_decode16?(binary, case: decode_case) :: boolean - def valid_decode16?(string, opts \\ []) + @spec valid16?(binary, case: decode_case) :: boolean + def valid16?(string, opts \\ []) - def valid_decode16?(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do + 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) @@ -365,7 +365,7 @@ defmodule Base do ArgumentError -> false end - def valid_decode16?(string, _opts) when is_binary(string) do + def valid16?(string, _opts) when is_binary(string) do false end @@ -643,19 +643,19 @@ defmodule Base do ## Examples - iex> Base.valid_decode64?("Zm9vYmFy") + iex> Base.valid64?("Zm9vYmFy") true - iex> Base.valid_decode64?("Zm9vYmFy\\n", ignore: :whitespace) + iex> Base.valid64?("Zm9vYmFy\\n", ignore: :whitespace) true - iex> Base.valid_decode64?("Zm9vYg==") + iex> Base.valid64?("Zm9vYg==") true """ @doc since: "1.19.0" - @spec valid_decode64?(binary, ignore: :whitespace, padding: boolean) :: boolean - def valid_decode64?(string, opts \\ []) when is_binary(string) do + @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 @@ -740,19 +740,19 @@ defmodule Base do ## Examples - iex> Base.valid_url_decode64?("_3_-_A==") + iex> Base.url_valid64?("_3_-_A==") true - iex> Base.valid_url_decode64?("_3_-_A==\\n", ignore: :whitespace) + iex> Base.url_valid64?("_3_-_A==\\n", ignore: :whitespace) true - iex> Base.valid_url_decode64?("_3_-_A", padding: false) + iex> Base.url_valid64?("_3_-_A", padding: false) true """ @doc since: "1.19.0" - @spec valid_url_decode64?(binary, ignore: :whitespace, padding: boolean) :: boolean - def valid_url_decode64?(string, opts \\ []) when is_binary(string) do + @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 @@ -1241,19 +1241,19 @@ defmodule Base do ## Examples - iex> Base.valid_decode32?("MZXW6YTBOI======") + iex> Base.valid32?("MZXW6YTBOI======") true - iex> Base.valid_decode32?("mzxw6ytboi======", case: :lower) + iex> Base.valid32?("mzxw6ytboi======", case: :lower) true - iex> Base.valid_decode32?("zzz") + iex> Base.valid32?("zzz") false """ @doc since: "1.19.0" - @spec valid_decode32?(binary, case: decode_case, padding: boolean) :: boolean() - def valid_decode32?(string, opts \\ []) when is_binary(string) do + @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 @@ -1379,19 +1379,19 @@ defmodule Base do ## Examples - iex> Base.valid_hex_decode32?("CPNMUOJ1E8======") + iex> Base.hex_valid32?("CPNMUOJ1E8======") true - iex> Base.valid_hex_decode32?("cpnmuoj1e8======", case: :lower) + iex> Base.hex_valid32?("cpnmuoj1e8======", case: :lower) true - iex> Base.valid_hex_decode32?("zzz", padding: false) + iex> Base.hex_valid32?("zzz", padding: false) false """ @doc since: "1.19.0" - @spec valid_hex_decode32?(binary, case: decode_case, padding: boolean) :: boolean - def valid_hex_decode32?(string, opts \\ []) when is_binary(string) do + @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 diff --git a/lib/elixir/test/elixir/base_test.exs b/lib/elixir/test/elixir/base_test.exs index 2021e4b66eb..aeb5f4b5c24 100644 --- a/lib/elixir/test/elixir/base_test.exs +++ b/lib/elixir/test/elixir/base_test.exs @@ -88,28 +88,28 @@ defmodule BaseTest do end end - test "valid_decode16?/1" do - assert valid_decode16?("") - assert valid_decode16?("66") - assert valid_decode16?("666F") - assert valid_decode16?("666F6F") - assert valid_decode16?("666F6F62") - assert valid_decode16?("666F6F6261") - assert valid_decode16?("666F6F626172") - assert valid_decode16?("A1B2C3D4E5F67891") - assert valid_decode16?("a1b2c3d4e5f67891", case: :lower) - assert valid_decode16?("a1B2c3D4e5F67891", case: :mixed) + 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 "valid_decode16?/1 returns false on non-alphabet character" do - refute valid_decode16?("66KF") - refute valid_decode16?("66ff") - refute valid_decode16?("66FF", case: :lower) - refute valid_decode16?("66fg", case: :mixed) + 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 "valid_decode16?/1 errors on odd-length string" do - refute valid_decode16?("666") + test "valid16?/1 errors on odd-length string" do + refute valid16?("666") end test "encode64/1 can deal with empty strings" do @@ -243,50 +243,50 @@ defmodule BaseTest do assert "Hello World" == decode64!("SGVsbG8gV29ybGQ", padding: false) end - test "valid_decode64?/1 can deal with empty strings" do - assert valid_decode64?("") + test "valid64?/1 can deal with empty strings" do + assert valid64?("") end - test "valid_decode64?/1 with two pads" do - assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") + test "valid64?/1 with two pads" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") end - test "valid_decode64?/1 with one pad" do - assert valid_decode64?("SGVsbG8gV29ybGQ=") + test "valid64?/1 with one pad" do + assert valid64?("SGVsbG8gV29ybGQ=") end - test "valid_decode64?/1 with no pad" do - assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft") + test "valid64?/1 with no pad" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft") end - test "valid_decode64?/1 returns false on non-alphabet character" do - refute valid_decode64?("Zm9)") + test "valid64?/1 returns false on non-alphabet character" do + refute valid64?("Zm9)") end - test "valid_decode64?/1 returns false on whitespace unless there's ignore: :whitespace" do - refute valid_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") + test "valid64?/1 returns false on whitespace unless there's ignore: :whitespace" do + refute valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") - assert valid_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) + assert valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) end - test "valid_decode64?/1 returns false on incorrect padding" do - refute valid_decode64?("SGVsbG8gV29ybGQ") + test "valid64?/1 returns false on incorrect padding" do + refute valid64?("SGVsbG8gV29ybGQ") end - test "valid_decode64?/2 with two pads and ignoring padding" do - assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) + test "valid64?/2 with two pads and ignoring padding" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) end - test "valid_decode64?/2 with one pad and ignoring padding" do - assert valid_decode64?("SGVsbG8gV29ybGQ", padding: false) + test "valid64?/2 with one pad and ignoring padding" do + assert valid64?("SGVsbG8gV29ybGQ", padding: false) end - test "valid_decode64?/2 with no pad and ignoring padding" do - assert valid_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) + test "valid64?/2 with no pad and ignoring padding" do + assert valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) end - test "valid_decode64?/2 with incorrect padding and ignoring padding" do - assert valid_decode64?("SGVsbG8gV29ybGQ", padding: false) + 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 @@ -426,54 +426,54 @@ defmodule BaseTest do assert "Hello World" == url_decode64!("SGVsbG8gV29ybGQ", padding: false) end - test "valid_url_decode64?/1 can deal with empty strings" do - assert valid_url_decode64?("") + test "url_valid64?/1 can deal with empty strings" do + assert url_valid64?("") end - test "valid_url_decode64?/1 with two pads" do - assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") + test "url_valid64?/1 with two pads" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ==") end - test "valid_url_decode64?/1 with one pad" do - assert valid_url_decode64?("SGVsbG8gV29ybGQ=") + test "url_valid64?/1 with one pad" do + assert url_valid64?("SGVsbG8gV29ybGQ=") end - test "valid_url_decode64?/1 with no pad" do - assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft") + test "url_valid64?/1 with no pad" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft") end - test "valid_url_decode64?/1 returns false on non-alphabet character" do - refute valid_url_decode64?("Zm9)") + test "url_valid64?/1 returns false on non-alphabet character" do + refute url_valid64?("Zm9)") end - test "valid_url_decode64?/1 returns false on whitespace unless there's ignore: :whitespace" do - refute valid_url_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") + test "url_valid64?/1 returns false on whitespace unless there's ignore: :whitespace" do + refute url_valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t") - assert valid_url_decode64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) + assert url_valid64?("\nQWxhZGRp bjpvcGVu\sIHNlc2Ft\t", ignore: :whitespace) end - test "valid_url_decode64?/1 returns false on incorrect padding" do - refute valid_url_decode64?("SGVsbG8gV29ybGQ") + test "url_valid64?/1 returns false on incorrect padding" do + refute url_valid64?("SGVsbG8gV29ybGQ") end - test "valid_url_decode64?/2 with two pads and ignoring padding" do - assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) + test "url_valid64?/2 with two pads and ignoring padding" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2FtZQ", padding: false) end - test "valid_url_decode64?/2 with one pad and ignoring padding" do - assert valid_url_decode64?("SGVsbG8gV29ybGQ", padding: false) + test "url_valid64?/2 with one pad and ignoring padding" do + assert url_valid64?("SGVsbG8gV29ybGQ", padding: false) end - test "valid_url_decode64?/2 with no pad and ignoring padding" do - assert valid_url_decode64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) + test "url_valid64?/2 with no pad and ignoring padding" do + assert url_valid64?("QWxhZGRpbjpvcGVuIHNlc2Ft", padding: false) end - test "valid_url_decode64?/2 errors on incorrect padding" do - refute valid_url_decode64?("SGVsbG8gV29ybGQ") + test "url_valid64?/2 errors on incorrect padding" do + refute url_valid64?("SGVsbG8gV29ybGQ") end - test "valid_url_decode64?/2 ignores incorrect padding when :padding is false" do - assert valid_url_decode64?("SGVsbG8gV29ybGQ", padding: false) + 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 @@ -689,79 +689,79 @@ defmodule BaseTest do "foob" = decode32!("MZXW6YQ", padding: false) end - test "valid_decode32?/1 can deal with empty strings" do - assert valid_decode32?("") + test "valid32?/1 can deal with empty strings" do + assert valid32?("") end - test "valid_decode32?/1 with one pad" do - assert valid_decode32?("MZXW6YQ=") + test "valid32?/1 with one pad" do + assert valid32?("MZXW6YQ=") end - test "valid_decode32?/1 with three pads" do - assert valid_decode32?("MZXW6===") + test "valid32?/1 with three pads" do + assert valid32?("MZXW6===") end - test "valid_decode32?/1 with four pads" do - assert valid_decode32?("MZXQ====") + test "valid32?/1 with four pads" do + assert valid32?("MZXQ====") end - test "valid_decode32?/1 with lowercase" do - assert valid_decode32?("mzxq====", case: :lower) + test "valid32?/1 with lowercase" do + assert valid32?("mzxq====", case: :lower) end - test "valid_decode32?/1 with mixed case" do - assert valid_decode32?("mZXq====", case: :mixed) + test "valid32?/1 with mixed case" do + assert valid32?("mZXq====", case: :mixed) end - test "valid_decode32?/1 with six pads" do - assert valid_decode32?("MZXW6YTBOI======") + test "valid32?/1 with six pads" do + assert valid32?("MZXW6YTBOI======") end - test "valid_decode32?/1 with no pads" do - assert valid_decode32?("MZXW6YTB") + test "valid32?/1 with no pads" do + assert valid32?("MZXW6YTB") end - test "valid_decode32?/1,2 returns false on non-alphabet character" do - refute valid_decode32?("MZX)6YTB") - refute valid_decode32?("66ff") - refute valid_decode32?("66FF", case: :lower) - refute valid_decode32?("0ZXW6YTB0I======", case: :mixed) + 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 "valid_decode32?/1 returns false on incorrect padding" do - refute valid_decode32?("MZXW6YQ") + test "valid32?/1 returns false on incorrect padding" do + refute valid32?("MZXW6YQ") end - test "valid_decode32?/2 with one pad and :padding to false" do - assert valid_decode32?("MZXW6YQ", padding: false) + test "valid32?/2 with one pad and :padding to false" do + assert valid32?("MZXW6YQ", padding: false) end - test "valid_decode32?/2 with three pads and ignoring padding" do - assert valid_decode32?("MZXW6", padding: false) + test "valid32?/2 with three pads and ignoring padding" do + assert valid32?("MZXW6", padding: false) end - test "valid_decode32?/2 with four pads and ignoring padding" do - assert valid_decode32?("MZXQ", padding: false) + test "valid32?/2 with four pads and ignoring padding" do + assert valid32?("MZXQ", padding: false) end - test "valid_decode32?/2 with :lower case and ignoring padding" do - assert valid_decode32?("mzxq", case: :lower, padding: false) + test "valid32?/2 with :lower case and ignoring padding" do + assert valid32?("mzxq", case: :lower, padding: false) end - test "valid_decode32?/2 with :mixed case and ignoring padding" do - assert valid_decode32?("mZXq", case: :mixed, padding: false) + test "valid32?/2 with :mixed case and ignoring padding" do + assert valid32?("mZXq", case: :mixed, padding: false) end - test "valid_decode32?/2 with six pads and ignoring padding" do - assert valid_decode32?("MZXW6YTBOI", padding: false) + test "valid32?/2 with six pads and ignoring padding" do + assert valid32?("MZXW6YTBOI", padding: false) end - test "valid_decode32?/2 with no pads and ignoring padding" do - assert valid_decode32?("MZXW6YTB", padding: false) + test "valid32?/2 with no pads and ignoring padding" do + assert valid32?("MZXW6YTB", padding: false) end - test "valid_decode32?/2 ignores incorrect padding when :padding is false" do - assert valid_decode32?("MZXW6YQ", padding: false) + 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 @@ -979,89 +979,89 @@ defmodule BaseTest do assert "fo" == hex_decode32!("cPNg", case: :mixed, padding: false) end - test "valid_hex_decode32?/1 can deal with empty strings" do - assert valid_hex_decode32?("") + test "hex_valid32?/1 can deal with empty strings" do + assert hex_valid32?("") end - test "valid_hex_decode32?/1 with one pad" do - assert valid_hex_decode32?("CPNMUOG=") + test "hex_valid32?/1 with one pad" do + assert hex_valid32?("CPNMUOG=") end - test "valid_hex_decode32?/1 with three pads" do - assert valid_hex_decode32?("CPNMU===") + test "hex_valid32?/1 with three pads" do + assert hex_valid32?("CPNMU===") end - test "valid_hex_decode32?/1 with four pads" do - assert valid_hex_decode32?("CPNG====") + test "hex_valid32?/1 with four pads" do + assert hex_valid32?("CPNG====") end - test "valid_hex_decode32?/1 with six pads" do - assert valid_hex_decode32?("CPNMUOJ1E8======") - assert valid_hex_decode32?("CO======") + test "hex_valid32?/1 with six pads" do + assert hex_valid32?("CPNMUOJ1E8======") + assert hex_valid32?("CO======") end - test "valid_hex_decode32?/1 with no pads" do - assert valid_hex_decode32?("CPNMUOJ1") + test "hex_valid32?/1 with no pads" do + assert hex_valid32?("CPNMUOJ1") end - test "valid_hex_decode32?/1,2 returns false on non-alphabet character" do - refute valid_hex_decode32?("CPN)UOJ1") - refute valid_hex_decode32?("66f") - refute valid_hex_decode32?("66F", case: :lower) + 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 "valid_hex_decode32?/1 returns false on incorrect padding" do - refute valid_hex_decode32?("CPNMUOG") + test "hex_valid32?/1 returns false on incorrect padding" do + refute hex_valid32?("CPNMUOG") end - test "valid_hex_decode32?/2 with lowercase" do - assert valid_hex_decode32?("cpng====", case: :lower) + test "hex_valid32?/2 with lowercase" do + assert hex_valid32?("cpng====", case: :lower) end - test "valid_hex_decode32?/2 with mixed case" do - assert valid_hex_decode32?("cPNg====", case: :mixed) + test "hex_valid32?/2 with mixed case" do + assert hex_valid32?("cPNg====", case: :mixed) end - test "valid_hex_decode32?/2 with one pad and ignoring padding" do - assert valid_hex_decode32?("CPNMUOG", padding: false) + test "hex_valid32?/2 with one pad and ignoring padding" do + assert hex_valid32?("CPNMUOG", padding: false) end - test "valid_hex_decode32?/2 with three pads and ignoring padding" do - assert valid_hex_decode32?("CPNMU", padding: false) + test "hex_valid32?/2 with three pads and ignoring padding" do + assert hex_valid32?("CPNMU", padding: false) end - test "valid_hex_decode32?/2 with four pads and ignoring padding" do - assert valid_hex_decode32?("CPNG", padding: false) + test "hex_valid32?/2 with four pads and ignoring padding" do + assert hex_valid32?("CPNG", padding: false) end - test "valid_hex_decode32?/2 with six pads and ignoring padding" do - assert valid_hex_decode32?("CPNMUOJ1E8", padding: false) + test "hex_valid32?/2 with six pads and ignoring padding" do + assert hex_valid32?("CPNMUOJ1E8", padding: false) end - test "valid_hex_decode32?/2 with no pads and ignoring padding" do - assert valid_hex_decode32?("CPNMUOJ1", padding: false) + test "hex_valid32?/2 with no pads and ignoring padding" do + assert hex_valid32?("CPNMUOJ1", padding: false) end - test "valid_hex_decode32?/2 ignores incorrect padding when :padding is false" do - assert valid_hex_decode32?("CPNMUOG", padding: false) + test "hex_valid32?/2 ignores incorrect padding when :padding is false" do + assert hex_valid32?("CPNMUOG", padding: false) end - test "valid_hex_decode32?/2 with :lower case and ignoring padding" do - assert valid_hex_decode32?("cpng", case: :lower, padding: false) + test "hex_valid32?/2 with :lower case and ignoring padding" do + assert hex_valid32?("cpng", case: :lower, padding: false) end - test "valid_hex_decode32?/2 with :mixed case and ignoring padding" do - assert valid_hex_decode32?("cPNg====", case: :mixed, padding: false) + 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, valid?} <- [ - {&encode16/2, &decode16!/2, &valid_decode16?/2}, - {&encode32/2, &decode32!/2, &valid_decode32?/2}, - {&hex_encode32/2, &hex_decode32!/2, &valid_hex_decode32?/2}, - {&encode64/2, &decode64!/2, &valid_decode64?/2}, - {&url_encode64/2, &url_decode64!/2, &valid_url_decode64?/2} + {&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],