diff --git a/apps/emqx/src/emqx_trie_search.erl b/apps/emqx/src/emqx_trie_search.erl index 4acee979f..ddeef966b 100644 --- a/apps/emqx/src/emqx_trie_search.erl +++ b/apps/emqx/src/emqx_trie_search.erl @@ -113,7 +113,7 @@ %% @doc Make a search-key for the given topic. -spec make_key(emqx_types:topic(), ID) -> key(ID). make_key(Topic, ID) when is_binary(Topic) -> - Words = words(Topic), + Words = filter_words(Topic), case emqx_topic:wildcard(Words) of true -> %% it's a wildcard @@ -171,7 +171,7 @@ matches(Topic, NextF, Opts) -> %% @doc Entrypoint of the search for a given topic. search(Topic, NextF, Opts) -> - Words = words(Topic), + Words = topic_words(Topic), Base = base_init(Words), ORetFirst = proplists:get_bool(return_first, Opts), OUnique = proplists:get_bool(unique, Opts), @@ -320,18 +320,22 @@ match_add(K, Acc) when is_list(Acc) -> match_add(K, first) -> throw({first, K}). --spec words(emqx_types:topic()) -> [word()]. -words(Topic) when is_binary(Topic) -> +-spec filter_words(emqx_types:topic()) -> [word()]. +filter_words(Topic) when is_binary(Topic) -> % NOTE % This is almost identical to `emqx_topic:words/1`, but it doesn't convert empty % tokens to ''. This is needed to keep ordering of words consistent with what % `match_filter/3` expects. - [word(W) || W <- emqx_topic:tokens(Topic)]. + [word(W, filter) || W <- emqx_topic:tokens(Topic)]. --spec word(binary()) -> word(). -word(<<"+">>) -> '+'; -word(<<"#">>) -> '#'; -word(Bin) -> Bin. +topic_words(Topic) when is_binary(Topic) -> + [word(W, topic) || W <- emqx_topic:tokens(Topic)]. + +word(<<"+">>, topic) -> error(badarg); +word(<<"#">>, topic) -> error(badarg); +word(<<"+">>, filter) -> '+'; +word(<<"#">>, filter) -> '#'; +word(Bin, _) -> Bin. %% match non-wildcard topics match_topics(Topic, {Topic, _} = Key, NextF, Acc) -> diff --git a/apps/emqx/test/emqx_trie_search_tests.erl b/apps/emqx/test/emqx_trie_search_tests.erl new file mode 100644 index 000000000..75994131d --- /dev/null +++ b/apps/emqx/test/emqx_trie_search_tests.erl @@ -0,0 +1,32 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% 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. +%%-------------------------------------------------------------------- + +-module(emqx_trie_search_tests). + +-include_lib("eunit/include/eunit.hrl"). + +topic_validation_test() -> + NextF = fun(_) -> '$end_of_table' end, + Call = fun(Topic) -> + emqx_trie_search:match(Topic, NextF) + end, + ?assertError(badarg, Call(<<"+">>)), + ?assertError(badarg, Call(<<"#">>)), + ?assertError(badarg, Call(<<"a/+/b">>)), + ?assertError(badarg, Call(<<"a/b/#">>)), + ?assertEqual(false, Call(<<"a/b/b+">>)), + ?assertEqual(false, Call(<<"a/b/c#">>)), + ok.