Skip to content

Optimize valid64? and url_valid64? #14434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 13, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 82 additions & 71 deletions lib/elixir/lib/base.ex
Original file line number Diff line number Diff line change
Expand Up @@ -657,10 +657,7 @@ defmodule Base 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
rescue
ArgumentError -> false
string |> remove_ignored(opts[:ignore]) |> validate64base?(pad?)
end

@doc """
Expand Down Expand Up @@ -754,110 +751,124 @@ defmodule Base 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
rescue
ArgumentError -> false
string |> remove_ignored(opts[:ignore]) |> validate64url?(pad?)
end

for {base, alphabet} <- [base: b64_alphabet, url: b64url_alphabet] do
decode_name = :"decode64#{base}!"
validate_name = :"validate64#{base}!"

validate_name = :"validate64#{base}?"
validate_main_name = :"validate_main64#{validate_name}?"
valid_char_name = :"valid_char64#{base}?"
{min, decoded} = alphabet |> Enum.with_index() |> to_decode_list.()

defp unquote(validate_name)(<<>>, _pad?) do
true
defp unquote(validate_main_name)(<<>>), do: true

defp unquote(validate_main_name)(
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8, rest::binary>>
) do
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7) and
unquote(valid_char_name)(c8) and
unquote(validate_main_name)(rest)
end

defp unquote(validate_name)(<<>>, _pad?), do: true

defp unquote(validate_name)(string, pad?) do
segs = div(byte_size(string) + 7, 8) - 1
<<main::size(^segs)-binary-unit(64), rest::binary>> = string

for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>> 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
main_valid? = unquote(validate_main_name)(main)

case rest do
_ when not main_valid? ->
false

<<c1::8, c2::8, ?=, ?=>> ->
unquote(decode_name)(c1)
unquote(decode_name)(c2)
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2)

<<c1::8, c2::8, c3::8, ?=>> ->
unquote(decode_name)(c1)
unquote(decode_name)(c2)
unquote(decode_name)(c3)
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3)

<<c1::8, c2::8, c3::8, c4::8>> ->
unquote(decode_name)(c1)
unquote(decode_name)(c2)
unquote(decode_name)(c3)
unquote(decode_name)(c4)
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4)

<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, ?=, ?=>> ->
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(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6)

<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
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(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7)

<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
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)
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7) and
unquote(valid_char_name)(c8)

<<c1::8, c2::8>> when not pad? ->
unquote(decode_name)(c1)
unquote(decode_name)(c2)
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2)

<<c1::8, c2::8, c3::8>> when not pad? ->
unquote(decode_name)(c1)
unquote(decode_name)(c2)
unquote(decode_name)(c3)
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3)

<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> 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(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6)

<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> 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)
unquote(valid_char_name)(c1) and
unquote(valid_char_name)(c2) and
unquote(valid_char_name)(c3) and
unquote(valid_char_name)(c4) and
unquote(valid_char_name)(c5) and
unquote(valid_char_name)(c6) and
unquote(valid_char_name)(c7)

_ ->
raise ArgumentError, "incorrect padding"
false
end
end

@compile {:inline, [{valid_char_name, 1}]}
defp unquote(valid_char_name)(char)
when elem({unquote_splicing(decoded)}, char - unquote(min)) != nil,
do: true

defp unquote(valid_char_name)(_char), do: false

defp unquote(decode_name)(char) do
index = char - unquote(min)

Expand Down
Loading