# Copyright 2019-2021 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. # # SPDX-FileCopyrightText: 2019-2021 Hubert Chathi # SPDX-License-Identifier: Apache-2.0 defmodule Polyjuice.Util do @typedoc ~S""" Represents a full event, including the event content, sender, event type, etc. Will have (at least) the following fields: `"type"`, `"content"`, `"sender"`, `"event_id"` (if a room event), and `"room_id"` (if a room event). """ @type event() :: %{String.t() => any} @typedoc ~S""" Represents the `content` field of an event. Its expected fields depend on the type of event. For example, a `m.room.message` event should have `"msgtype"` and `"body"` fields (though implementations should be prepared to deal with malformed events). """ @type event_content() :: %{String.t() => any} @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 @doc ~S""" Make a verify key of the type given by the key ID. Examples: iex> Polyjuice.Util.make_verify_key("a4qcdbuJoRwy1ykc7Xhj1wTQroEXHdr04AY2Ds6SPb0", "ed25519:1") %Polyjuice.Util.Ed25519.VerifyKey{ key: <<107, 138, 156, 117, 187, 137, 161, 28, 50, 215, 41, 28, 237, 120, 99, 215, 4, 208, 174, 129, 23, 29, 218, 244, 224, 6, 54, 14, 206, 146, 61, 189>>, id: "1" } """ @spec make_verify_key(key :: String.t(), id :: String.t()) :: Polyjuice.Util.VerifyKey.t() | nil def make_verify_key(key, <<"ed25519:", id::binary>>) do Polyjuice.Util.Ed25519.VerifyKey.from_base64(key, id) end def make_verify_key(_, _) do nil end end