From 07ad6a0a39b6c866f4d13ca5fe86a17500f97c7f Mon Sep 17 00:00:00 2001 From: preciz Date: Thu, 22 Oct 2026 11:44:33 +0200 Subject: [PATCH] Avoid per-byte binary construction and backtracking in Plug.Conn.Utils.token/1 - Track byte length and return the original binary directly (or slice via binary_part/3 when trailing whitespace is present) instead of appending byte-by-byte into a writable binary for unquoted tokens. - Initialize the binary match context in do_token/2 so unquoted_token/3 and quoted_token/2 resume the match context without per-iteration header checks. - Fold opening quote handling (len == 0) and empty binary rejection (len != 0) into unquoted_token/3 to avoid upfront empty-binary checks and first-byte backtracking. - Split multi-byte escape and CRLF patterns (<> and <>) and move <<>> base clauses last so the compiler does not emit per-byte bs_get_position/bs_set_position instructions. --- lib/plug/conn/utils.ex | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/plug/conn/utils.ex b/lib/plug/conn/utils.ex index aee135f31..105b8c49d 100644 --- a/lib/plug/conn/utils.ex +++ b/lib/plug/conn/utils.ex @@ -239,23 +239,42 @@ defmodule Plug.Conn.Utils do """ @spec token(binary) :: binary | false - def token(""), do: false - def token(<>), do: quoted_token(quoted, "") - def token(token), do: unquoted_token(token, "") + def token(token) when is_binary(token), do: do_token(token, token) + + defp do_token(<>, original), do: unquoted_token(token, original, 0) - defp quoted_token(<<>>, _acc), do: false defp quoted_token(<>, acc), do: strip_spaces(t) == "" and acc - defp quoted_token(<>, acc), do: quoted_token(t, <>) + + defp quoted_token(<>, acc) do + case rest do + <> -> quoted_token(t, <>) + <<>> -> false + end + end + defp quoted_token(<>, acc), do: quoted_token(t, <>) + defp quoted_token(<<>>, _acc), do: false + + defp unquoted_token(<>, _token, len), + do: len == 0 and quoted_token(quoted, "") + + defp unquoted_token(<>, token, len) do + case rest do + <> -> strip_spaces(t) == "" and binary_part(token, 0, len) + _ -> false + end + end - defp unquoted_token(<<>>, acc), do: acc - defp unquoted_token("\r\n" <> t, acc), do: strip_spaces(t) == "" and acc - defp unquoted_token(<>, acc) when h in @space, do: strip_spaces(t) == "" and acc + defp unquoted_token(<>, token, len) when h in @space, + do: strip_spaces(t) == "" and binary_part(token, 0, len) - defp unquoted_token(<>, _acc) when h in @specials or h < 32 or h === 127, + defp unquoted_token(<>, _token, _len) when h in @specials or h < 32 or h === 127, do: false - defp unquoted_token(<>, acc), do: unquoted_token(t, <>) + defp unquoted_token(<<_, t::binary>>, token, len), + do: unquoted_token(t, token, len + 1) + + defp unquoted_token(<<>>, token, len), do: len != 0 and token @doc """ Parses a comma-separated list of header values.