Packages
erldns
4.3.0
11.1.0
11.0.3
11.0.2
11.0.1
11.0.0
10.6.0
10.5.6
10.5.5
10.5.4
10.5.3
10.5.2
10.5.1
10.5.0
10.4.4
10.4.3
10.4.2
10.4.1
10.4.0
10.3.0
10.2.1
10.2.0
10.1.0
10.0.0
10.0.0-rc4
10.0.0-rc3
10.0.0-rc2
10.0.0-rc1
9.1.0
9.0.0
9.0.0-rc3
9.0.0-rc2
9.0.0-rc1
8.1.0
8.0.0
8.0.0-rc6
8.0.0-rc5
8.0.0-rc4
8.0.0-rc3
8.0.0-rc2
8.0.0-rc1
7.0.0
7.0.0-rc9
7.0.0-rc8
7.0.0-rc7
7.0.0-rc6
7.0.0-rc5
7.0.0-rc4
7.0.0-rc3
7.0.0-rc2
7.0.0-rc12
7.0.0-rc11
7.0.0-rc10
7.0.0-rc1
6.0.2
6.0.1
6.0.0
5.0.0
4.3.1
4.3.0
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.2
4.1.1
4.1.0
4.0.0
3.0.0
1.0.0
Erlang Authoritative DNS Server
Current section
Files
Jump to
Current section
Files
src/erldns_txt.erl
%% Copyright (c) 2012-2020, DNSimple Corporation
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% @doc Functions related to TXT record parsing.
-module(erldns_txt).
-include("erldns.hrl").
-export([parse/1]).
-define(MAX_TXT_SIZE, 255).
-ifdef(TEST).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
-endif.
% Public API
%% @doc Parse a string into a collection of bit strings, each no longer than
%% 255 characters.
-spec parse(binary() | string()) -> [] | [[binary()]].
parse(Binary) when is_binary(Binary) ->
parse(binary_to_list(Binary));
parse([]) ->
[];
parse([C | Rest]) ->
parse_char([C | Rest], C, Rest, [], false).
-spec parse(String :: string(), Rest :: string(), Tokens :: [[binary()]], Escaped :: boolean()) -> [binary()] | [[binary()]].
parse(String, [], [], _) ->
[split(String)];
parse(_, [], Tokens, _) ->
Tokens;
parse(String, [C | Rest], Tokens, Escaped) ->
parse_char(String, C, Rest, Tokens, Escaped).
parse(_, [], Tokens, CurrentToken, true) ->
% Last character is escaped
Tokens ++ [CurrentToken];
parse(String, [C | Rest], Tokens, CurrentToken, Escaped) ->
parse_char(String, C, Rest, Tokens, CurrentToken, Escaped).
%% @doc Do something with the given character. The rules are as follows:
%% * A quote starts/ends a token.
%% * Any other character is considered part of the current token.
-spec parse_char(string(), char(), string(), [string()], boolean()) -> any().
parse_char(String, $", Rest, Tokens, _) ->
parse(String, Rest, Tokens, [], false);
parse_char(String, _, Rest, Tokens, _) ->
parse(String, Rest, Tokens, false).
parse_char(String, $", Rest, Tokens, CurrentToken, false) ->
parse(String, Rest, Tokens ++ [split(CurrentToken)], false);
parse_char(String, $", Rest, Tokens, CurrentToken, true) ->
parse(String, Rest, Tokens, CurrentToken ++ [$"], false);
parse_char(String, $\\, Rest, Tokens, CurrentToken, false) ->
parse(String, Rest, Tokens, CurrentToken, true);
parse_char(String, $\\, Rest, Tokens, CurrentToken, true) ->
parse(String, Rest, Tokens, CurrentToken ++ [$\\], false);
parse_char(String, C, Rest, Tokens, CurrentToken, _) ->
parse(String, Rest, Tokens, CurrentToken ++ [C], false).
%% @doc Split the given string into a list of bit strings, with each element
%% limited to 255 characters or less.
-spec split(string()) -> [binary()].
split(Data) ->
split(Data, []).
%% Internal recursive split function.
-spec split(string(), [binary()]) -> [binary()].
split(Data, Parts) ->
case byte_size(list_to_binary(Data)) > ?MAX_TXT_SIZE of
true ->
First = list_to_binary(string:substr(Data, 1, ?MAX_TXT_SIZE)),
Rest = string:substr(Data, ?MAX_TXT_SIZE + 1),
case Rest of
[] ->
Parts ++ [First];
_ ->
split(Rest, Parts ++ [First])
end;
false ->
Parts ++ [list_to_binary(Data)]
end.
-ifdef(TEST).
%% Known failure cases:
%% \
%% "
%% "\"
parse_test() ->
?assertEqual([], parse("")),
?assertEqual([[<<"test">>]], parse("test")),
?assertEqual([[list_to_binary(lists:duplicate(255, "x")), list_to_binary(lists:duplicate(15, "x"))]], parse(lists:duplicate(270, "x"))),
?assertEqual([[<<"test">>]], parse(<<"test">>)),
?assertEqual([[<<"test">>], [<<"test">>]], parse("\"test\" \"test\"")),
?assertEqual([[<<"\\">>]], parse("\\")),
?assertEqual([[<<"test\\;">>]], parse("test\\;")),
?assertEqual([[<<"test\\">>]], parse("test\\")).
%?assertEqual(parse("\"test\"\""), [[<<"test\"">>]]).
proper_test_() ->
[] = proper:module(?MODULE, [{to_file, user}, {numtests, 1000}]).
check_bblist([]) ->
true;
check_bblist([[Binary] | Rest]) when is_binary(Binary) andalso size(Binary) =< ?MAX_TXT_SIZE ->
check_bblist(Rest);
check_bblist(_) ->
false.
%% ASCII Strings without " nor end in \
quoteless_ascii_string1() ->
list(oneof([integer(0, 33), integer(35, 255)])).
quoteless_ascii_string() ->
?SUCHTHAT(
String,
quoteless_ascii_string1(),
begin
case lists:reverse(String) of
[92 | _] ->
false;
_ ->
true
end
end
).
quoted_ascii_string1() ->
%% " has character code 34.
?LET(String, quoteless_ascii_string(), [34] ++ String ++ [34]).
quoted_ascii_string() ->
?SUCHTHAT(
String,
quoted_ascii_string1(),
begin
case lists:reverse(String) of
% "\
[34, 92 | _] ->
false;
_ ->
true
end
end
).
quoted_and_unquoted_ascii_string_unflattened() ->
list(oneof([quoted_ascii_string(), quoteless_ascii_string()])).
quoted_and_unquoted_ascii_string() ->
?LET(StringList, quoted_and_unquoted_ascii_string_unflattened(), lists:flatten(StringList)).
prop_parse_holds_type() ->
?FORALL(
ASCIIString,
quoted_and_unquoted_ascii_string(),
begin
BinaryOfBinaryList = parse(ASCIIString),
check_bblist(BinaryOfBinaryList)
end
).
-endif.