Fix topic filter matching

- re.escape() the whole filter string first to escape _all_ regex
  metacharacters in it, not just $. (# and + are both regex metacharacters,
  so their replace expressions now need a leading \\ to replace the
  escaping, too.)
- # matches topics both with and without a trailing /, so the replace
  expressions adds a '?' before the '.*'. The .lstrip('?') at the end removes
  this in case the # was the first character in the filter.
- + should only match a single level, but it should _also_ match empty levels,
  so use '[^/]*' to replace it.
- Use Regex.fullmatch() to match against the whole topic string, not just
  its start.

Also add two unit tests to test this matching, and fix an incorrect match
against + in test_client_subscribe_publish_dollar_topic_2.
pull/42/head
lrasku 2020-10-08 21:58:26 +03:00 zatwierdzone przez Marius Kriegerowski
rodzic 7921e2cc22
commit c41b01499d
2 zmienionych plików z 42 dodań i 5 usunięć

Wyświetl plik

@ -847,11 +847,12 @@ class Broker:
else:
# else use regex
match_pattern = re.compile(
a_filter.replace("#", ".*")
.replace("$", "\$")
.replace("+", "[/\$\s\w\d]+")
re.escape(a_filter)
.replace("\\#", "?.*")
.replace("\\+", "[^/]*")
.lstrip("?")
)
return match_pattern.match(topic)
return match_pattern.fullmatch(topic)
async def _broadcast_loop(self):
running_tasks = deque()

Wyświetl plik

@ -446,7 +446,7 @@ async def test_client_subscribe_publish_dollar_topic_2(broker):
ret = await sub_client.subscribe([("+/monitor/Clients", QOS_0)])
assert ret == [QOS_0]
await _client_publish("/test/monitor/Clients", b"data", QOS_0)
await _client_publish("test/monitor/Clients", b"data", QOS_0)
message = await sub_client.deliver_message()
assert message is not None
@ -500,3 +500,39 @@ async def _client_publish(topic, data, qos, retain=False):
ret = await pub_client.publish(topic, data, qos, retain)
await pub_client.disconnect()
return ret
def test_matches_multi_level_wildcard(broker):
test_filter = "sport/tennis/player1/#"
for bad_topic in [
"sport/tennis",
"sport/tennis/",
]:
assert not broker.matches(bad_topic, test_filter)
for good_topic in [
"sport/tennis/player1",
"sport/tennis/player1/",
"sport/tennis/player1/ranking",
"sport/tennis/player1/score/wimbledon",
]:
assert broker.matches(good_topic, test_filter)
def test_matches_single_level_wildcard(broker):
test_filter = "sport/tennis/+"
for bad_topic in [
"sport/tennis",
"sport/tennis/player1/",
"sport/tennis/player1/ranking",
]:
assert not broker.matches(bad_topic, test_filter)
for good_topic in [
"sport/tennis/",
"sport/tennis/player1",
"sport/tennis/player2",
]:
assert broker.matches(good_topic, test_filter)