|
@@ -0,0 +1,145 @@
|
|
|
+%%% coding: utf-8
|
|
|
+%%% @doc
|
|
|
+%%% sal_prep profile helper module that will validate a utf-8
|
|
|
+%%% string based on sasl_prep profile as defined in
|
|
|
+%%% https://tools.ietf.org/html/rfc4013
|
|
|
+%%% @end
|
|
|
+
|
|
|
+-module(epgsql_sasl_prep_profile).
|
|
|
+
|
|
|
+-export([validate/1]).
|
|
|
+
|
|
|
+validate(Str) ->
|
|
|
+ lists:foreach(fun(F) ->
|
|
|
+ lists:any(F, unicode:characters_to_list(Str, utf8))
|
|
|
+ andalso error({non_valid_scram_password, Str})
|
|
|
+ end, [ fun is_non_asci_space_character/1
|
|
|
+ , fun is_ascii_control_character/1
|
|
|
+ , fun is_non_ascii_control_character/1
|
|
|
+ , fun is_private_use_characters/1
|
|
|
+ , fun is_non_character_code_points/1
|
|
|
+ , fun is_surrogate_code_points/1
|
|
|
+ , fun is_inappropriate_for_plain_text_char/1
|
|
|
+ , fun is_inappropriate_for_canonical_representation_char/1
|
|
|
+ , fun is_change_display_properties_or_deprecated_char/1
|
|
|
+ , fun is_tagging_char/1 ]),
|
|
|
+ Str.
|
|
|
+
|
|
|
+%% @doc Return true if the given character is a non-ASCII space character
|
|
|
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.1.2
|
|
|
+is_non_asci_space_character(C) ->
|
|
|
+ C == 16#00A0
|
|
|
+ orelse C == 16#1680
|
|
|
+ orelse (16#2000 =< C andalso C =< 16#200B)
|
|
|
+ orelse C == 16#202F
|
|
|
+ orelse C == 16#205F
|
|
|
+ orelse C == 16#3000.
|
|
|
+
|
|
|
+%% @doc Return true if the given character is an ASCII control character
|
|
|
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.2.1
|
|
|
+is_ascii_control_character(C) ->
|
|
|
+ C =< 16#001F orelse C == 16#007F.
|
|
|
+
|
|
|
+%% @doc Return true if the given character is a non-ASCII control character
|
|
|
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.2.2
|
|
|
+is_non_ascii_control_character(C) ->
|
|
|
+ (16#0080 =< C andalso C =< 16#009F)
|
|
|
+ orelse C == 16#06DD
|
|
|
+ orelse C == 16#070F
|
|
|
+ orelse C == 16#180E
|
|
|
+ orelse C == 16#200C
|
|
|
+ orelse C == 16#200D
|
|
|
+ orelse C == 16#2028
|
|
|
+ orelse C == 16#2029
|
|
|
+ orelse C == 16#2060
|
|
|
+ orelse C == 16#2061
|
|
|
+ orelse C == 16#2062
|
|
|
+ orelse C == 16#2063
|
|
|
+ orelse (16#206A =< C andalso C =< 16#206F)
|
|
|
+ orelse C == 16#FEFF
|
|
|
+ orelse (16#FFF9 =< C andalso C =< 16#FFFC)
|
|
|
+ orelse (16#1D173 =< C andalso C =< 16#1D17A).
|
|
|
+
|
|
|
+%% @doc Return true if the given character is a private use character
|
|
|
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.3
|
|
|
+is_private_use_characters(C) ->
|
|
|
+ (16#E000 =< C andalso C =< 16#F8FF)
|
|
|
+ orelse (16#F000 =< C andalso C =< 16#FFFFD)
|
|
|
+ orelse (16#100000 =< C andalso C =< 16#10FFFD).
|
|
|
+
|
|
|
+%% @doc Return true if the given character is a non-character code point
|
|
|
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.4
|
|
|
+is_non_character_code_points(C) ->
|
|
|
+ (16#FDD0 =< C andalso C =< 16#FDEF)
|
|
|
+ orelse (16#FFFE =< C andalso C =< 16#FFFF)
|
|
|
+ orelse (16#1FFFE =< C andalso C =< 16#1FFFF)
|
|
|
+ orelse (16#2FFFE =< C andalso C =< 16#2FFFF)
|
|
|
+ orelse (16#3FFFE =< C andalso C =< 16#3FFFF)
|
|
|
+ orelse (16#4FFFE =< C andalso C =< 16#4FFFF)
|
|
|
+ orelse (16#5FFFE =< C andalso C =< 16#5FFFF)
|
|
|
+ orelse (16#6FFFE =< C andalso C =< 16#6FFFF)
|
|
|
+ orelse (16#7FFFE =< C andalso C =< 16#7FFFF)
|
|
|
+ orelse (16#8FFFE =< C andalso C =< 16#8FFFF)
|
|
|
+ orelse (16#9FFFE =< C andalso C =< 16#9FFFF)
|
|
|
+ orelse (16#AFFFE =< C andalso C =< 16#AFFFF)
|
|
|
+ orelse (16#BFFFE =< C andalso C =< 16#BFFFF)
|
|
|
+ orelse (16#CFFFE =< C andalso C =< 16#CFFFF)
|
|
|
+ orelse (16#DFFFE =< C andalso C =< 16#DFFFF)
|
|
|
+ orelse (16#EFFFE =< C andalso C =< 16#EFFFF)
|
|
|
+ orelse (16#FFFFE =< C andalso C =< 16#FFFFF)
|
|
|
+ orelse (16#10FFFE =< C andalso C =< 16#10FFFF).
|
|
|
+
|
|
|
+%% @doc Return true if the given character is a surrogate code point as defined by
|
|
|
+%% https://tools.ietf.org/html/rfc3454#appendix-C.5
|
|
|
+is_surrogate_code_points(C) ->
|
|
|
+ 16#D800 =< C andalso C =< 16#DFFF.
|
|
|
+
|
|
|
+%% @doc Return true if the given character is inappropriate for plain text characters
|
|
|
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.6
|
|
|
+is_inappropriate_for_plain_text_char(C) ->
|
|
|
+ C == 16#FFF9
|
|
|
+ orelse C == 16#FFFA
|
|
|
+ orelse C == 16#FFFB
|
|
|
+ orelse C == 16#FFFC
|
|
|
+ orelse C == 16#FFFD.
|
|
|
+
|
|
|
+%% @doc Return true if the given character is inappropriate for canonical representation
|
|
|
+%% as defined by https://tools.ietf.org/html/rfc3454#appendix-C.7
|
|
|
+is_inappropriate_for_canonical_representation_char(C) ->
|
|
|
+ 16#2FF0 =< C andalso C =< 16#2FFB.
|
|
|
+
|
|
|
+%% @doc Return true if the given character is change display properties or deprecated
|
|
|
+%% characters as defined by https://tools.ietf.org/html/rfc3454#appendix-C.8
|
|
|
+is_change_display_properties_or_deprecated_char(C) ->
|
|
|
+ C == 16#0340
|
|
|
+ orelse C == 16#0341
|
|
|
+ orelse C == 16#200E
|
|
|
+ orelse C == 16#200F
|
|
|
+ orelse C == 16#202A
|
|
|
+ orelse C == 16#202B
|
|
|
+ orelse C == 16#202C
|
|
|
+ orelse C == 16#202D
|
|
|
+ orelse C == 16#202E
|
|
|
+ orelse C == 16#206A
|
|
|
+ orelse C == 16#206B
|
|
|
+ orelse C == 16#206C
|
|
|
+ orelse C == 16#206D
|
|
|
+ orelse C == 16#206E
|
|
|
+ orelse C == 16#206F.
|
|
|
+
|
|
|
+%% @doc Return true if the given character is a tagging character as defined by
|
|
|
+%% https://tools.ietf.org/html/rfc3454#appendix-C.9
|
|
|
+is_tagging_char(C) ->
|
|
|
+ C == 16#E0001 orelse
|
|
|
+ (16#E0020 =< C andalso C =< 16#E007F).
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+-include_lib("eunit/include/eunit.hrl").
|
|
|
+
|
|
|
+normalize_test() ->
|
|
|
+ ?assertEqual(<<"123 !~">>, validate(<<"123 !~">>)),
|
|
|
+ ?assertEqual(<<"привет"/utf8>>, validate(<<"привет"/utf8>>)),
|
|
|
+ ?assertEqual(<<"Χαίρετε"/utf8>>, validate(<<"Χαίρετε"/utf8>>)),
|
|
|
+ ?assertEqual(<<"你好"/utf8>>, validate(<<"你好"/utf8>>)).
|
|
|
+
|
|
|
+-endif.
|