# Copyright 2019 Hubert Chathi # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. defmodule Polyjuice.Util do @doc ~S""" Escape a user ID localpart so that it only uses the allowable character set. The method used is defined in https://matrix.org/docs/spec/appendices#mapping-from-other-character-sets By default, it will escape upper-case letters. If you want upper-case letters to simply be lower-cased, use the `fold_case: true` option. Examples: iex> Polyjuice.Util.escape_localpart("abc\x01 !\",-./09:;<=>?@AZ[\\]^_`az{|}~á") "abc=01=20=21=22=2c-./09=3a=3b=3c=3d=3e=3f=40_a_z=5b=5c=5d=5e__=60az=7b=7c=7d=7e=c3=a1" iex> Polyjuice.Util.escape_localpart("abc\x01 !\",-./09:;<=>?@AZ[\\]^_`az{|}~á", fold_case: true) "abc=01=20=21=22=2c-./09=3a=3b=3c=3d=3e=3f=40az=5b=5c=5d=5e_=60az=7b=7c=7d=7e=c3=a1" """ def escape_localpart(localpart, opts \\ []) when is_binary(localpart) and is_list(opts) do fold_case = Keyword.get(opts, :fold_case, false) do_escape(localpart, fold_case) |> IO.iodata_to_binary() end defp do_escape("", _), do: "" defp do_escape(<> <> rest, fold_case) when c < ?- or (c > ?9 and c < ?A) or (c > ?Z and c != ?_ and c < ?a) or c > ?z do if c < 0xF do ["=0", Integer.to_string(c, 16) |> String.downcase() | do_escape(rest, fold_case)] else [?=, Integer.to_string(c, 16) |> String.downcase() | do_escape(rest, fold_case)] end end defp do_escape(<> <> rest, fold_case) when c >= ?A and c <= ?Z do if fold_case do [c - ?A + ?a | do_escape(rest, fold_case)] else [?_, c - ?A + ?a | do_escape(rest, fold_case)] end end defp do_escape(<> <> rest, false) do ["__" | do_escape(rest, false)] end defp do_escape(string, fold_case) do size = chunk_size(string, fold_case, 0) <> = string [chunk | do_escape(rest, fold_case)] end defp chunk_size("", _, acc), do: acc defp chunk_size(<> <> _, _, acc) when c < ?- or (c > ?9 and c != ?= and c < ?A) or (c > ?Z and c != ?_ and c < ?a) or c > ?z do acc end defp chunk_size(<> <> _, false, acc) when (c >= ?A and c <= ?Z) or c == ?_ do acc end defp chunk_size(<<_char>> <> rest, fold_case, acc) do chunk_size(rest, fold_case, acc + 1) end end