@@ -731,6 +731,19 @@ class AsyncWebServerRequest {
731731};
732732
733733class AsyncURIMatcher {
734+ private:
735+ // Matcher types are internal, not part of public API
736+ enum class Type {
737+ None, // default state: matcher does not match anything
738+ Auto, // parse _uri at construct time and infer match type(s): _uri may be transformed to remove wildcards
739+ All, // matches everything
740+ Exact, // matches equivalent to regex: ^{_uri}$
741+ Prefix, // matches equivalent to regex: ^{_uri}.*
742+ Extension, // non-regular match: /pattern../*.ext
743+ BackwardCompatible, // matches equivalent to regex: ^{_uri}(/.*)?$
744+ Regex, // matches _url as regex
745+ };
746+
734747public:
735748 /**
736749 * @brief No special matching behavior (default)
@@ -762,9 +775,9 @@ class AsyncURIMatcher {
762775 static constexpr uint16_t CaseInsensitive = (1 << 0);
763776
764777 // public constructors
765- AsyncURIMatcher() : _flags(All ) {}
766- AsyncURIMatcher(String uri, uint16_t modifiers = None) : AsyncURIMatcher(std::move(uri), Auto, modifiers) {}
767- AsyncURIMatcher(const char *uri, uint16_t modifiers = None) : AsyncURIMatcher(String(uri), Auto, modifiers) {}
778+ AsyncURIMatcher() : _flags(intptr_t(Type::None) ) {}
779+ AsyncURIMatcher(String uri, uint16_t modifiers = None) : AsyncURIMatcher(std::move(uri), Type:: Auto, modifiers) {}
780+ AsyncURIMatcher(const char *uri, uint16_t modifiers = None) : AsyncURIMatcher(String(uri), Type:: Auto, modifiers) {}
768781
769782#ifdef ASYNCWEBSERVER_REGEX
770783 AsyncURIMatcher(const AsyncURIMatcher &c) : _value(c._value), _flags(c._flags) {
@@ -774,7 +787,7 @@ class AsyncURIMatcher {
774787 }
775788
776789 AsyncURIMatcher(AsyncURIMatcher &&c) : _value(std::move(c._value)), _flags(c._flags) {
777- c._flags = 0 ;
790+ c._flags = intptr_t(Type::None) ;
778791 }
779792
780793 ~AsyncURIMatcher() {
@@ -810,7 +823,7 @@ class AsyncURIMatcher {
810823 _flags = r._flags;
811824 if (r._isRegex()) {
812825 // We have adopted it
813- r._flags = 0 ;
826+ r._flags = intptr_t(Type::None) ;
814827 }
815828 return *this;
816829 }
@@ -825,11 +838,6 @@ class AsyncURIMatcher {
825838#endif
826839
827840 bool matches(AsyncWebServerRequest *request) const {
828- // Match-all is tested first
829- if (_flags & All) {
830- return true;
831- }
832-
833841#ifdef ASYNCWEBSERVER_REGEX
834842 if (_isRegex()) {
835843 std::smatch matches;
@@ -843,34 +851,33 @@ class AsyncURIMatcher {
843851 return false;
844852 }
845853#endif
846- String path = request->url();
847- if (_flags & (CaseInsensitive << 16)) {
848- path.toLowerCase();
849- }
850854
851- // Exact match (should be the most common case)
852- if (( _flags & Exact) && (_value == path)) {
853- return true;
854- }
855+ // extract matcher type from _flags
856+ const intptr_t flags = _flags >> 1; // shift off disambiguation bit;
857+ const Type type = static_cast<Type>(flags & 0xFFFF); // Type is lower 16 bits
858+ const uint16_t modifiers = flags >> 16; // Modifiers are upper 16 bits
855859
856- // Prefix match types
857- if ((_flags & Prefix) && path.startsWith(_value)) {
858- return true;
859- }
860- if ((_flags & PrefixFolder) && path.startsWith(_value + "/")) {
861- return true;
860+ // apply modifiers
861+ String path = request->url();
862+ if (modifiers & CaseInsensitive) {
863+ path.toLowerCase();
862864 }
863865
864- // Extension match
865- if (_flags & Extension) {
866- int split = _value.lastIndexOf("/*.");
867- if (split >= 0 && path.startsWith(_value.substring(0, split)) && path.endsWith(_value.substring(split + 2))) {
868- return true;
866+ switch (type) {
867+ case Type::All: return true;
868+ case Type::Exact: return (_value == path);
869+ case Type::Prefix: return path.startsWith(_value);
870+ case Type::Extension:
871+ {
872+ int split = _value.lastIndexOf("/*.");
873+ return (split >= 0 && path.startsWith(_value.substring(0, split)) && path.endsWith(_value.substring(split + 2)));
869874 }
875+ case Type::BackwardCompatible: return (_value == path) || path.startsWith(_value + "/");
876+ default:
877+ // Should never happen - programming error
878+ assert("Invalid type");
879+ return false;
870880 }
871-
872- // we did not match
873- return false;
874881 }
875882
876883 // static factory methods for common match types
@@ -882,7 +889,7 @@ class AsyncURIMatcher {
882889 * Usage: server.on(AsyncURIMatcher::all(), handler);
883890 */
884891 static inline AsyncURIMatcher all() {
885- return AsyncURIMatcher{{}, All, None};
892+ return AsyncURIMatcher{{}, Type:: All, None};
886893 }
887894
888895 /**
@@ -897,7 +904,7 @@ class AsyncURIMatcher {
897904 * Doesn't match: "/LOGIN" (unless CaseInsensitive flag used)
898905 */
899906 static inline AsyncURIMatcher exact(String c, uint16_t modifiers = None) {
900- return AsyncURIMatcher{std::move(c), Exact, modifiers};
907+ return AsyncURIMatcher{std::move(c), Type:: Exact, modifiers};
901908 }
902909
903910 /**
@@ -911,7 +918,7 @@ class AsyncURIMatcher {
911918 * Note: This is pure prefix matching - does NOT require folder separator
912919 */
913920 static inline AsyncURIMatcher prefix(String c, uint16_t modifiers = None) {
914- return AsyncURIMatcher{std::move(c), Prefix, modifiers};
921+ return AsyncURIMatcher{std::move(c), Type:: Prefix, modifiers};
915922 }
916923
917924 /**
@@ -929,12 +936,12 @@ class AsyncURIMatcher {
929936 static inline AsyncURIMatcher dir(String c, uint16_t modifiers = None) {
930937 // Pre-calculate folder for efficiency
931938 if (!c.length()) {
932- return AsyncURIMatcher{"/", Prefix, modifiers};
939+ return AsyncURIMatcher{"/", Type:: Prefix, modifiers};
933940 }
934941 if (c[c.length() - 1] != '/') {
935942 c.concat('/');
936943 }
937- return AsyncURIMatcher{std::move(c), Prefix, modifiers};
944+ return AsyncURIMatcher{std::move(c), Type:: Prefix, modifiers};
938945 }
939946
940947 /**
@@ -951,7 +958,7 @@ class AsyncURIMatcher {
951958 * The path before "/\*." must match exactly, and the URI must end with the extension.
952959 */
953960 static inline AsyncURIMatcher ext(String c, uint16_t modifiers = None) {
954- return AsyncURIMatcher{std::move(c), Extension, modifiers};
961+ return AsyncURIMatcher{std::move(c), Type:: Extension, modifiers};
955962 }
956963
957964#ifdef ASYNCWEBSERVER_REGEX
@@ -970,33 +977,15 @@ class AsyncURIMatcher {
970977 * Performance note: Regex matching is slower than other match types.
971978 */
972979 static inline AsyncURIMatcher regex(String c, uint16_t modifiers = None) {
973- return AsyncURIMatcher{std::move(c), Regex, modifiers};
980+ return AsyncURIMatcher{std::move(c), Type:: Regex, modifiers};
974981 }
975982#endif
976983
977984private:
978- // Matcher types
979- enum Type : uint16_t {
980- // Meta types - low bits
981- Auto = (1 << 0), // parse _uri at construct time and infer match type(s)
982- // (_uri may be transformed to remove wildcards)
983-
984- All = (1 << 1), // No type set
985- Exact = (1 << 2), // matches equivalent to regex: ^{_uri}$
986- Prefix = (1 << 3), // matches equivalent to regex: ^{_uri}.*
987- PrefixFolder = (1 << 4), // matches equivalent to regex: ^{_uri}/.*
988- Extension = (1 << 5), // non-regular match: /pattern../*.ext
989-
990- #ifdef ASYNCWEBSERVER_REGEX
991- NonRegex = (1 << 0), // bit to use as pointer tag
992- Regex = (1 << 15), // matches _url as regex
993- #endif
994- };
995-
996985 // fields
997986 String _value;
998987 union {
999- intptr_t _flags;
988+ intptr_t _flags; // type and flags packed together
1000989#ifdef ASYNCWEBSERVER_REGEX
1001990 // Overlay the pattern pointer storage with the type. It is treated as a tagged pointer:
1002991 // if any of the LSBs are set, it stores type, as a valid object must be aligned and so
@@ -1018,40 +1007,42 @@ class AsyncURIMatcher {
10181007#endif
10191008
10201009 // Core private constructor
1021- AsyncURIMatcher(String uri, Type type = Auto , uint16_t modifiers = None ) : _value(std::move(uri)), _flags(uint32_t(modifiers) << 16 | type ) {
1010+ AsyncURIMatcher(String uri, Type type, uint16_t modifiers) : _value(std::move(uri)) {
10221011#ifdef ASYNCWEBSERVER_REGEX
1023- if ((type & Regex) || ((type & Auto) && _value.startsWith("^") && _value.endsWith("$"))) {
1012+ if ((type == Type:: Regex) || ((type == Type:: Auto) && _value.startsWith("^") && _value.endsWith("$"))) {
10241013 pattern = new std::regex(_value.c_str(), (modifiers & CaseInsensitive) ? (std::regex::icase | std::regex::optimize) : (std::regex::optimize));
1025- return; // no additional processing - modifiers are overwritten
1014+ return; // no additional processing - flags are overwritten by pattern pointer
10261015 }
10271016#endif
10281017 if (modifiers & CaseInsensitive) {
10291018 _value.toLowerCase();
10301019 }
1031- if (type & Auto) {
1020+ if (type == Type:: Auto) {
10321021 // Inspect _value to set flags
10331022 // empty URI matches everything
10341023 if (!_value.length()) {
1035- _flags = All;
1036- return; // Does not require extra bit for regex disambiguation
1037- }
1038- if (_value.endsWith("*")) {
1024+ type = Type::All;
1025+ } else if (_value.endsWith("*")) {
10391026 // wildcard match with * at the end
1040- _flags |= Prefix;
1027+ type = Type:: Prefix;
10411028 _value = _value.substring(0, _value.length() - 1);
10421029 } else if (_value.lastIndexOf("/*.") >= 0) {
10431030 // prefix match with /*.ext
10441031 // matches any path ending with .ext
10451032 // e.g. /images/*.png will match /images/pic.png and /images/2023/pic.png but not /img/pic.png
1046- _flags |= Extension;
1033+ type = Type:: Extension;
10471034 } else {
1048- // No special values - use default of folder and exact
1049- _flags |= PrefixFolder | Exact ;
1035+ // backward compatible use case: exact match or prefix with trailing /
1036+ type = Type::BackwardCompatible ;
10501037 }
10511038 }
1052- #ifdef ASYNCWEBSERVER_REGEX
1053- _flags |= NonRegex; // disambiguate regex case
1054- #endif
1039+ _flags = uint32_t(modifiers) << 16 | uint16_t(type);
1040+ // Use lsb to disambiguate from regex pointer in the case where someone has regex activated but uses a non-regex type.
1041+ // We always do this shift, even if regex is not enabled, to keep the layout identical and also catch programmatic errors earlier.
1042+ // For example a mistake is to set a modifier flag to (1 << 15), which is the msb of the uint16_t.
1043+ // This msb is discarded during this shift operation.
1044+ // So pay attention to not have more than 15 modifier flags.
1045+ _flags = (_flags << 1) + 1;
10551046 }
10561047};
10571048
0 commit comments