topic_validation.hpp 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. //
  2. // Copyright (c) 2023-2025 Ivica Siladic, Bruno Iljazovic, Korina Simicevic
  3. //
  4. // Distributed under the Boost Software License, Version 1.0.
  5. // (See accompanying file LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. #ifndef BOOST_MQTT5_TOPIC_VALIDATION_HPP
  8. #define BOOST_MQTT5_TOPIC_VALIDATION_HPP
  9. #include <boost/mqtt5/detail/utf8_mqtt.hpp>
  10. #include <cstdint>
  11. #include <string_view>
  12. namespace boost::mqtt5::detail {
  13. static constexpr int32_t min_subscription_identifier = 1;
  14. static constexpr int32_t max_subscription_identifier = 268'435'455;
  15. static constexpr std::string_view shared_sub_prefix = "$share/";
  16. inline bool is_utf8_no_wildcard(validation_result result) {
  17. return result == validation_result::valid;
  18. }
  19. inline bool is_not_empty(size_t sz) {
  20. return sz != 0;
  21. }
  22. inline bool is_valid_topic_size(size_t sz) {
  23. return is_not_empty(sz) && is_valid_string_size(sz);
  24. }
  25. inline validation_result validate_topic_name(std::string_view str) {
  26. return validate_impl(str, is_valid_topic_size, is_utf8_no_wildcard);
  27. }
  28. inline validation_result validate_topic_alias_name(std::string_view str) {
  29. return validate_impl(str, is_valid_string_size, is_utf8_no_wildcard);
  30. }
  31. inline validation_result validate_shared_topic_name(std::string_view str) {
  32. return validate_impl(str, is_not_empty, is_utf8_no_wildcard);
  33. }
  34. inline validation_result validate_topic_filter(std::string_view str) {
  35. if (!is_valid_topic_size(str.size()))
  36. return validation_result::invalid;
  37. constexpr int multi_lvl_wildcard = '#';
  38. constexpr int single_lvl_wildcard = '+';
  39. // must be the last character preceded by '/' or stand alone
  40. // #, .../#
  41. if (str.back() == multi_lvl_wildcard) {
  42. str.remove_suffix(1);
  43. if (!str.empty() && str.back() != '/')
  44. return validation_result::invalid;
  45. }
  46. int last_c = -1;
  47. validation_result result;
  48. while (!str.empty()) {
  49. int c = pop_front_unichar(str);
  50. // can be used at any level, but must occupy an entire level
  51. // +, +/..., .../+/..., .../+
  52. bool is_valid_single_lvl = (c == single_lvl_wildcard) &&
  53. (str.empty() || str.front() == '/') &&
  54. (last_c == -1 || last_c == '/');
  55. result = validate_mqtt_utf8_char(c);
  56. if (
  57. result == validation_result::valid ||
  58. is_valid_single_lvl
  59. ) {
  60. last_c = c;
  61. continue;
  62. }
  63. return validation_result::invalid;
  64. }
  65. return validation_result::valid;
  66. }
  67. inline validation_result validate_shared_topic_filter(
  68. std::string_view str, bool wildcard_allowed = true
  69. ) {
  70. if (!is_valid_topic_size(str.size()))
  71. return validation_result::invalid;
  72. if (str.compare(0, shared_sub_prefix.size(), shared_sub_prefix) != 0)
  73. return validation_result::invalid;
  74. str.remove_prefix(shared_sub_prefix.size());
  75. size_t share_name_end = str.find_first_of('/');
  76. if (share_name_end == std::string::npos)
  77. return validation_result::invalid;
  78. validation_result result;
  79. result = validate_shared_topic_name(str.substr(0, share_name_end));
  80. if (result != validation_result::valid)
  81. return validation_result::invalid;
  82. auto topic_filter = str.substr(share_name_end + 1);
  83. return wildcard_allowed ?
  84. validate_topic_filter(topic_filter) :
  85. validate_topic_name(topic_filter)
  86. ;
  87. }
  88. } // end namespace boost::mqtt5::detail
  89. #endif //BOOST_MQTT5_TOPIC_VALIDATION_HPP