/* Copyright (C) 2019 * * This file is part of the osdev components suite * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include "mqttutil.h" // boost #include #include namespace osdev { namespace components { namespace mqtt { bool isValidTopic( const std::string &topic ) { if (topic.empty() || topic.size() > 65535) { return false; } auto posHash = topic.find('#'); if (std::string::npos != posHash && posHash < topic.size() - 1) { return false; } std::size_t pos = 0; while ((pos = topic.find('+', pos)) != std::string::npos) { if (pos > 0 && topic[pos - 1] != '/') { return false; } if (pos < topic.size() - 1 && topic[pos + 1] != '/') { return false; } ++pos; } return true; } bool hasWildcard( const std::string &topic ) { return ( topic.size() > 0 && (topic.find( '+' ) != std::string::npos || topic.size() - 1 == '#' ) ); } bool testForOverlap( const std::string &existingTopic, const std::string &newTopic ) { if (existingTopic == newTopic) { return true; } // A topic that starts with a $ can never overlap with topic that does not start with a $. // Topics that start!!! with a $ are so called system reserved topics and not meant for users to publish to. if ((existingTopic[0] == '$' && newTopic[0] != '$') || (existingTopic[0] != '$' && newTopic[0] == '$')) { return false; } std::vector existingTopicList; std::vector newTopicList; boost::algorithm::split(existingTopicList, existingTopic, [](char ch) { return ch == '/'; }); boost::algorithm::split(newTopicList, newTopic, [](char ch) { return ch == '/'; }); auto szExistingTopicList = existingTopicList.size(); auto szNewTopicList = newTopicList.size(); // Walk through the topic term by term until it is proved for certain that the topics either have no overlap // or do have overlap. for (std::size_t idx = 0; idx < std::minmax(szExistingTopicList, szNewTopicList).first; ++idx) { if ("#" == existingTopicList[idx] || "#" == newTopicList[idx]) { return true; // Match all wildcard found so there is always a possible overlap. } else if ("+" == existingTopicList[idx] || "+" == newTopicList[idx]) { // these terms can match each other based on wildcard. // Topics are still in the race for overlap, proceed to the next term } else if (existingTopicList[idx] != newTopicList[idx]) { return false; // no match possible because terms are not wildcards and differ from each other. } else { // term is an exact match. The topics are still in the race for overlap, proceed to the next term. } } // Still no certain prove of overlap. If the number of terms for both topics are the same at this point then they overlap. // If they are not the same than a match all wildcard on the longer topic can still make them match because of a special rule // that states that a topic with a match all wildcard also matches a topic without that term. // Example: topic /level/1 matches wildcard topic /level/1/# // A match single wildcard at the end or at the term before a match all wildcard must also be taken into account. // Example: /level/+ can overlap with /level/1/# // /level/1 can overlap with /level/+/# if (szNewTopicList != szExistingTopicList) { if (szNewTopicList == szExistingTopicList + 1 && "#" == newTopicList[szNewTopicList - 1]) { return (newTopicList[szNewTopicList - 2] == existingTopicList[szExistingTopicList - 1] || "+" == existingTopicList[szExistingTopicList - 1] || "+" == newTopicList[szNewTopicList - 2]); } if (szExistingTopicList == szNewTopicList + 1 && "#" == existingTopicList[szExistingTopicList - 1]) { return (existingTopicList[szExistingTopicList - 2] == newTopicList[szNewTopicList - 1] || "+" == newTopicList[szNewTopicList - 1] || "+" == existingTopicList[szExistingTopicList - 2]); } return false; } return true; } std::string convertTopicToRegex(const std::string& topic) { // escape the regex characters in msgTemplate static const boost::regex esc("[.^$|()\\[\\]{}*+?\\\\]"); static const std::string rep("\\\\$&"); // $&, refers to whatever is matched by the whole expression std::string out = boost::regex_replace(topic, esc, rep); static const boost::regex multiTopicRegex("#$"); static const boost::regex singleTopicRegex(R"((/|^|(?<=/))\\\+(/|$))"); out = boost::regex_replace(out, multiTopicRegex, ".*"); return boost::regex_replace(out, singleTopicRegex, "$1[^/]*?$2"); } } // End namespace mqtt } // End namespace components } // End namespace osdev