From 86ffa3a3ea1fb742116766680a95a22e7753d79a Mon Sep 17 00:00:00 2001 From: Deomid Ryabkov Date: Mon, 21 Jan 2019 17:36:42 +0000 Subject: [PATCH] Properly support MQTT wildcards when matching topics CL: mg: Properly support MQTT wildcards when matching topics PUBLISHED_FROM=aa60a71d488f4b56dff0951886c8a9a4066fd4f2 --- mongoose.c | 50 ++++++++++++++++++++++++++++++++---------------- src/mg_mqtt.c | 50 ++++++++++++++++++++++++++++++++---------------- test/unit_test.c | 37 +++++++++++++++++++++++++++-------- 3 files changed, 97 insertions(+), 40 deletions(-) diff --git a/mongoose.c b/mongoose.c index a7851188..2b6cd18d 100644 --- a/mongoose.c +++ b/mongoose.c @@ -10977,26 +10977,44 @@ static void mg_mqtt_proto_data_destructor(void *proto_data) { MG_FREE(proto_data); } +static struct mg_str mg_mqtt_next_topic_component(struct mg_str *topic) { + struct mg_str res = *topic; + const char *c = mg_strchr(*topic, '/'); + if (c != NULL) { + res.len = (c - topic->p); + topic->len -= (res.len + 1); + topic->p += (res.len + 1); + } else { + topic->len = 0; + } + return res; +} + +/* Refernce: https://mosquitto.org/man/mqtt-7.html */ int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic) { - /* TODO(mkm): implement real matching */ - if (memchr(exp.p, '#', exp.len)) { - /* exp `foo/#` will become `foo/` */ - exp.len -= 1; - /* - * topic should be longer than the expression: e.g. topic `foo/bar` does - * match `foo/#`, but neither `foo` nor `foo/` do. - */ - if (topic.len <= exp.len) { + struct mg_str ec, tc; + if (exp.len == 0) return 0; + while (1) { + ec = mg_mqtt_next_topic_component(&exp); + tc = mg_mqtt_next_topic_component(&topic); + if (ec.len == 0) { + if (tc.len != 0) return 0; + if (exp.len == 0) break; + continue; + } + if (mg_vcmp(&ec, "+") == 0) { + if (tc.len == 0 && topic.len == 0) return 0; + continue; + } + if (mg_vcmp(&ec, "#") == 0) { + /* Must be the last component in the expression or it's invalid. */ + return (exp.len == 0); + } + if (mg_strcmp(ec, tc) != 0) { return 0; } - - /* Truncate topic so that it'll pass the next length check */ - topic.len = exp.len; } - if (topic.len != exp.len) { - return 0; - } - return strncmp(topic.p, exp.p, exp.len) == 0; + return (tc.len == 0 && topic.len == 0); } int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic) { diff --git a/src/mg_mqtt.c b/src/mg_mqtt.c index 5c0b194e..68069e8d 100644 --- a/src/mg_mqtt.c +++ b/src/mg_mqtt.c @@ -196,26 +196,44 @@ static void mg_mqtt_proto_data_destructor(void *proto_data) { MG_FREE(proto_data); } +static struct mg_str mg_mqtt_next_topic_component(struct mg_str *topic) { + struct mg_str res = *topic; + const char *c = mg_strchr(*topic, '/'); + if (c != NULL) { + res.len = (c - topic->p); + topic->len -= (res.len + 1); + topic->p += (res.len + 1); + } else { + topic->len = 0; + } + return res; +} + +/* Refernce: https://mosquitto.org/man/mqtt-7.html */ int mg_mqtt_match_topic_expression(struct mg_str exp, struct mg_str topic) { - /* TODO(mkm): implement real matching */ - if (memchr(exp.p, '#', exp.len)) { - /* exp `foo/#` will become `foo/` */ - exp.len -= 1; - /* - * topic should be longer than the expression: e.g. topic `foo/bar` does - * match `foo/#`, but neither `foo` nor `foo/` do. - */ - if (topic.len <= exp.len) { + struct mg_str ec, tc; + if (exp.len == 0) return 0; + while (1) { + ec = mg_mqtt_next_topic_component(&exp); + tc = mg_mqtt_next_topic_component(&topic); + if (ec.len == 0) { + if (tc.len != 0) return 0; + if (exp.len == 0) break; + continue; + } + if (mg_vcmp(&ec, "+") == 0) { + if (tc.len == 0 && topic.len == 0) return 0; + continue; + } + if (mg_vcmp(&ec, "#") == 0) { + /* Must be the last component in the expression or it's invalid. */ + return (exp.len == 0); + } + if (mg_strcmp(ec, tc) != 0) { return 0; } - - /* Truncate topic so that it'll pass the next length check */ - topic.len = exp.len; } - if (topic.len != exp.len) { - return 0; - } - return strncmp(topic.p, exp.p, exp.len) == 0; + return (tc.len == 0 && topic.len == 0); } int mg_mqtt_vmatch_topic_expression(const char *exp, struct mg_str topic) { diff --git a/test/unit_test.c b/test/unit_test.c index 84f9f8bc..96c9e459 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -3380,15 +3380,36 @@ static const char *test_mqtt_parse_mqtt_qos1(void) { } static const char *test_mqtt_match_topic_expression(void) { - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo")), 1); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/")), 0); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/bar")), 0); + ASSERT(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("/foo", mg_mk_str("/foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/foo", mg_mk_str("/foo"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foobar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo//"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/bar"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo", mg_mk_str("foo/+"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/bar", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/+", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/bar", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/+", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/+/bar", mg_mk_str("foo//bar"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("foo/+/+", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/+/#", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("+/foo/bar", mg_mk_str("/foo/bar"))); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo")), 0); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/")), 0); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar")), 1); - ASSERT_EQ(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar/baz")), - 1); + ASSERT(!mg_mqtt_vmatch_topic_expression("", mg_mk_str(""))); + ASSERT(mg_mqtt_vmatch_topic_expression("/", mg_mk_str(""))); + ASSERT(mg_mqtt_vmatch_topic_expression("/", mg_mk_str("/"))); + + ASSERT(mg_mqtt_vmatch_topic_expression("#", mg_mk_str(""))); + ASSERT(mg_mqtt_vmatch_topic_expression("#", mg_mk_str("foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("#", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar"))); + ASSERT(mg_mqtt_vmatch_topic_expression("foo/#", mg_mk_str("foo/bar/baz"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("#/foo", mg_mk_str("foo"))); + ASSERT(!mg_mqtt_vmatch_topic_expression("#/foo", mg_mk_str("bar/foo"))); return NULL; }