View Javadoc
1   package net.anyflow.lannister.topic;
2   
3   import java.io.UnsupportedEncodingException;
4   import java.util.List;
5   
6   import com.google.common.base.Strings;
7   
8   import net.anyflow.lannister.Settings;
9   
10  public class TopicMatcher {
11  
12  	private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(TopicMatcher.class);
13  
14  	private static final String SEPARATOR = "/";
15  	private static final String MULTI_LEVEL_WILDCARD = "#";
16  	private static final String SINGLE_LEVEL_WILDCARD = "+";
17  
18  	private static final int MIN_LENGTH = 1;
19  	private static final int MAX_LENGTH = 65535;
20  	private static final char NULL = '\u0000';
21  
22  	private static final int INDEX_NOT_FOUND = -1;
23  
24  	private static final List<String> BANNED_TOPICFILTERS = Settings.INSTANCE.bannedTopicFilters();
25  
26  	public static final String MULTI_LEVEL_WILDCARD_PATTERN = SEPARATOR + MULTI_LEVEL_WILDCARD;
27  	public static final String TOPIC_WILDCARDS = MULTI_LEVEL_WILDCARD + SINGLE_LEVEL_WILDCARD;
28  
29  	public static boolean isValid(String topicFilter, boolean allowWildcard) {
30  		if (topicFilter == null) { return false; }
31  		if (BANNED_TOPICFILTERS.contains(topicFilter)) { return false; }
32  
33  		int length = 0;
34  		try {
35  			length = topicFilter.getBytes("UTF-8").length;
36  		}
37  		catch (UnsupportedEncodingException e) {
38  			logger.error(e.getMessage(), e);
39  			return false;
40  		}
41  
42  		if (length < MIN_LENGTH || length > MAX_LENGTH) {
43  			logger.error("Invalid topic length [length={}", length);
44  			return false;
45  		}
46  
47  		if (allowWildcard) {
48  			if (topicFilter.equals(MULTI_LEVEL_WILDCARD) || topicFilter.equals(SINGLE_LEVEL_WILDCARD)) { return true; }
49  
50  			if (countMatches(topicFilter, MULTI_LEVEL_WILDCARD) > 1 || (topicFilter.contains(MULTI_LEVEL_WILDCARD)
51  					&& !topicFilter.endsWith(MULTI_LEVEL_WILDCARD_PATTERN))) {
52  				logger.error("Invalid usage of multi-level wildcard in topic string: {}", topicFilter);
53  				return false;
54  			}
55  		}
56  
57  		return isValidSingleLevelWildcard(topicFilter);
58  	}
59  
60  	private static int countMatches(String str, String sub) {
61  		if (Strings.isNullOrEmpty(str) || Strings.isNullOrEmpty(sub)) { return 0; }
62  
63  		int count = 0;
64  		int idx = 0;
65  		while ((idx = str.indexOf(sub, idx)) != INDEX_NOT_FOUND) {
66  			count++;
67  			idx += sub.length();
68  		}
69  
70  		return count;
71  	}
72  
73  	private static boolean isValidSingleLevelWildcard(String topicString) {
74  		char singleLevelWildcardChar = SINGLE_LEVEL_WILDCARD.charAt(0);
75  		char topicLevelSeparatorChar = SEPARATOR.charAt(0);
76  
77  		char[] chars = topicString.toCharArray();
78  		int length = chars.length;
79  		char prev = NULL;
80  		char next = NULL;
81  
82  		for (int i = 0; i < length; i++) {
83  			prev = (i - 1 >= 0) ? chars[i - 1] : NULL;
84  			next = (i + 1 < length) ? chars[i + 1] : NULL;
85  
86  			if ((chars[i] == singleLevelWildcardChar) && (prev != topicLevelSeparatorChar && prev != NULL
87  					|| next != topicLevelSeparatorChar && next != NULL)) {
88  				logger.error("Invalid usage of single-level wildcard in topic string '{}'!", topicString);
89  				return false;
90  			}
91  		}
92  
93  		return true;
94  	}
95  
96  	public static boolean match(String topicFilter, String topicName) {
97  		if (!TopicMatcher.isValid(topicFilter, true) || !TopicMatcher.isValid(topicName, false)) { return false; }
98  
99  		if ((topicFilter.startsWith(MULTI_LEVEL_WILDCARD) || topicFilter.startsWith(SINGLE_LEVEL_WILDCARD))
100 				&& topicName.startsWith("$")) { return false; } // [MQTT-4.7.2-1]
101 
102 		String[] tfs = topicFilter.split("/");
103 		String[] tns = topicName.split("/");
104 
105 		for (int i = 0; i < tns.length; ++i) {
106 			if (i >= tfs.length) {
107 				return false;
108 			}
109 			else if (tfs[i].equals(MULTI_LEVEL_WILDCARD)) {
110 				return true;
111 			}
112 			else if (tfs[i].equals(SINGLE_LEVEL_WILDCARD)) {
113 				continue;
114 			}
115 			else if (tfs[i].equals(tns[i])) {
116 				continue;
117 			}
118 			else {
119 				return false;
120 			}
121 		}
122 
123 		return true;
124 	}
125 }