Skip to content

Commit dedb7f9

Browse files
committed
Issue #545 - Validating the Topic provided when a response topic is set.
Signed-off-by: James Sutton <james.sutton@uk.ibm.com>
1 parent a3e3efa commit dedb7f9

12 files changed

Lines changed: 238 additions & 292 deletions

File tree

org.eclipse.paho.mqttv5.client/src/main/java/org/eclipse/paho/mqttv5/client/MqttAsyncClient.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode;
6666
import org.eclipse.paho.mqttv5.common.packet.MqttSubscribe;
6767
import org.eclipse.paho.mqttv5.common.packet.MqttUnsubscribe;
68+
import org.eclipse.paho.mqttv5.common.util.MqttTopicValidator;
6869

6970
/**
7071
* Lightweight client for talking to an MQTT server using non-blocking methods
@@ -1134,7 +1135,7 @@ public String getCurrentServerURI() {
11341135
* if the topic contains a '+' or '#' wildcard character.
11351136
*/
11361137
protected MqttTopic getTopic(String topic) {
1137-
MqttTopic.validate(topic, false/* wildcards NOT allowed */);
1138+
MqttTopicValidator.validate(topic, false/* wildcards NOT allowed */);
11381139

11391140
MqttTopic result = (MqttTopic) topics.get(topic);
11401141
if (result == null) {
@@ -1255,7 +1256,7 @@ public IMqttToken subscribe(MqttSubscription[] subscriptions, Object userContext
12551256
subs.append(subscriptions[i].toString());
12561257

12571258
// Check if the topic filter is valid before subscribing
1258-
MqttTopic.validate(subscriptions[i].getTopic(), true/* allow wildcards */);
1259+
MqttTopicValidator.validate(subscriptions[i].getTopic(), true/* allow wildcards */);
12591260
}
12601261
// @TRACE 106=Subscribe topicFilter={0} userContext={1} callback={2}
12611262
log.fine(CLASS_NAME, methodName, "106", new Object[] { subs.toString(), userContext, callback });
@@ -1459,7 +1460,7 @@ public IMqttToken unsubscribe(String[] topicFilters, Object userContext, MqttAct
14591460
// Although we already checked when subscribing, but invalid
14601461
// topic filter is meanless for unsubscribing, just prohibit it
14611462
// to reduce unnecessary control packet send to broker.
1462-
MqttTopic.validate(topicFilters[i], true/* allow wildcards */);
1463+
MqttTopicValidator.validate(topicFilters[i], true/* allow wildcards */);
14631464
}
14641465

14651466
// remove message handlers from the list for this client
@@ -1588,7 +1589,7 @@ public IMqttDeliveryToken publish(String topic, MqttMessage message, Object user
15881589
log.fine(CLASS_NAME, methodName, "111", new Object[] { topic, userContext, callback });
15891590

15901591
// Checks if a topic is valid when publishing a message.
1591-
MqttTopic.validate(topic, false/* wildcards NOT allowed */);
1592+
MqttTopicValidator.validate(topic, false/* wildcards NOT allowed */);
15921593

15931594
MqttDeliveryToken token = new MqttDeliveryToken(getClientId());
15941595
token.setActionCallback(callback);

org.eclipse.paho.mqttv5.client/src/main/java/org/eclipse/paho/mqttv5/client/MqttConnectionOptions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.eclipse.paho.mqttv5.common.MqttMessage;
3131
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
3232
import org.eclipse.paho.mqttv5.common.packet.UserProperty;
33+
import org.eclipse.paho.mqttv5.common.util.MqttTopicValidator;
3334

3435
/**
3536
* Holds the set of options that control how the client connects to a server.
@@ -201,7 +202,7 @@ public void setWill(String topic, MqttMessage message) {
201202
if (topic == null || message == null || message.getPayload() == null) {
202203
throw new IllegalArgumentException();
203204
}
204-
MqttTopic.validate(topic, false); // Wildcards are not allowed
205+
MqttTopicValidator.validate(topic, false); // Wildcards are not allowed
205206
this.willDestination = topic;
206207
this.willMessage = message;
207208
// Prevent any more changes to the will message

org.eclipse.paho.mqttv5.client/src/main/java/org/eclipse/paho/mqttv5/client/MqttTopic.java

Lines changed: 0 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
*/
1616
package org.eclipse.paho.mqttv5.client;
1717

18-
import java.io.UnsupportedEncodingException;
19-
2018
import org.eclipse.paho.mqttv5.client.internal.ClientComms;
21-
import org.eclipse.paho.mqttv5.client.util.Strings;
2219
import org.eclipse.paho.mqttv5.common.MqttException;
2320
import org.eclipse.paho.mqttv5.common.MqttMessage;
2421
import org.eclipse.paho.mqttv5.common.MqttPersistenceException;
@@ -30,40 +27,6 @@
3027
*/
3128
public class MqttTopic {
3229

33-
/**
34-
* The forward slash (/) is used to separate each level within a topic tree and
35-
* provide a hierarchical structure to the topic space. The use of the topic
36-
* level separator is significant when the two wildcard characters are
37-
* encountered in topics specified by subscribers.
38-
*/
39-
public static final String TOPIC_LEVEL_SEPARATOR = "/";
40-
41-
/**
42-
* Multi-level wildcard The number sign (#) is a wildcard character that matches
43-
* any number of levels within a topic.
44-
*/
45-
public static final String MULTI_LEVEL_WILDCARD = "#";
46-
47-
/**
48-
* Single-level wildcard The plus sign (+) is a wildcard character that matches
49-
* only one topic level.
50-
*/
51-
public static final String SINGLE_LEVEL_WILDCARD = "+";
52-
53-
/**
54-
* Multi-level wildcard pattern(/#)
55-
*/
56-
public static final String MULTI_LEVEL_WILDCARD_PATTERN = TOPIC_LEVEL_SEPARATOR + MULTI_LEVEL_WILDCARD;
57-
58-
/**
59-
* Topic wildcards (#+)
60-
*/
61-
public static final String TOPIC_WILDCARDS = MULTI_LEVEL_WILDCARD + SINGLE_LEVEL_WILDCARD;
62-
63-
// topic name and topic filter length range defined in the spec
64-
private static final int MIN_TOPIC_LEN = 1;
65-
private static final int MAX_TOPIC_LEN = 65535;
66-
private static final char NUL = '\u0000';
6730

6831
private ClientComms comms;
6932
private String name;
@@ -159,159 +122,4 @@ public String toString() {
159122
return getName();
160123
}
161124

162-
/**
163-
* Validate the topic name or topic filter
164-
*
165-
* @param topicString
166-
* topic name or filter
167-
* @param wildcardAllowed
168-
* true if validate topic filter, false otherwise
169-
* @throws IllegalArgumentException
170-
* if the topic is invalid
171-
*/
172-
public static void validate(String topicString, boolean wildcardAllowed) throws IllegalArgumentException {
173-
int topicLen = 0;
174-
try {
175-
topicLen = topicString.getBytes("UTF-8").length;
176-
} catch (UnsupportedEncodingException e) {
177-
throw new IllegalStateException(e.getMessage());
178-
}
179-
180-
// Spec: length check
181-
// - All Topic Names and Topic Filters MUST be at least one character
182-
// long
183-
// - Topic Names and Topic Filters are UTF-8 encoded strings, they MUST
184-
// NOT encode to more than 65535 bytes
185-
if (topicLen < MIN_TOPIC_LEN || topicLen > MAX_TOPIC_LEN) {
186-
throw new IllegalArgumentException(String.format("Invalid topic length, should be in range[%d, %d]!",
187-
new Object[] { new Integer(MIN_TOPIC_LEN), new Integer(MAX_TOPIC_LEN) }));
188-
}
189-
190-
// *******************************************************************************
191-
// 1) This is a topic filter string that can contain wildcard characters
192-
// *******************************************************************************
193-
if (wildcardAllowed) {
194-
// Only # or +
195-
if (Strings.equalsAny(topicString, new String[] { MULTI_LEVEL_WILDCARD, SINGLE_LEVEL_WILDCARD })) {
196-
return;
197-
}
198-
199-
// 1) Check multi-level wildcard
200-
// Rule:
201-
// The multi-level wildcard can be specified only on its own or next
202-
// to the topic level separator character.
203-
204-
// - Can only contains one multi-level wildcard character
205-
// - The multi-level wildcard must be the last character used within
206-
// the topic tree
207-
if (Strings.countMatches(topicString, MULTI_LEVEL_WILDCARD) > 1
208-
|| (topicString.contains(MULTI_LEVEL_WILDCARD)
209-
&& !topicString.endsWith(MULTI_LEVEL_WILDCARD_PATTERN))) {
210-
throw new IllegalArgumentException(
211-
"Invalid usage of multi-level wildcard in topic string: " + topicString);
212-
}
213-
214-
// 2) Check single-level wildcard
215-
// Rule:
216-
// The single-level wildcard can be used at any level in the topic
217-
// tree, and in conjunction with the
218-
// multilevel wildcard. It must be used next to the topic level
219-
// separator, except when it is specified on
220-
// its own.
221-
validateSingleLevelWildcard(topicString);
222-
223-
return;
224-
}
225-
226-
// *******************************************************************************
227-
// 2) This is a topic name string that MUST NOT contains any wildcard characters
228-
// *******************************************************************************
229-
if (Strings.containsAny(topicString, TOPIC_WILDCARDS)) {
230-
throw new IllegalArgumentException("The topic name MUST NOT contain any wildcard characters (#+)");
231-
}
232-
}
233-
234-
private static void validateSingleLevelWildcard(String topicString) {
235-
char singleLevelWildcardChar = SINGLE_LEVEL_WILDCARD.charAt(0);
236-
char topicLevelSeparatorChar = TOPIC_LEVEL_SEPARATOR.charAt(0);
237-
238-
char[] chars = topicString.toCharArray();
239-
int length = chars.length;
240-
char prev = NUL, next = NUL;
241-
for (int i = 0; i < length; i++) {
242-
prev = (i - 1 >= 0) ? chars[i - 1] : NUL;
243-
next = (i + 1 < length) ? chars[i + 1] : NUL;
244-
245-
if (chars[i] == singleLevelWildcardChar) {
246-
// prev and next can be only '/' or none
247-
if (prev != topicLevelSeparatorChar && prev != NUL || next != topicLevelSeparatorChar && next != NUL) {
248-
throw new IllegalArgumentException(
249-
String.format("Invalid usage of single-level wildcard in topic string '%s'!",
250-
new Object[] { topicString }));
251-
252-
}
253-
}
254-
}
255-
}
256-
257-
/**
258-
* Check the supplied topic name and filter match
259-
*
260-
* @param topicFilter
261-
* topic filter: wildcards allowed
262-
* @param topicName
263-
* topic name: wildcards not allowed
264-
* @return true if the topic matches the filter
265-
* @throws IllegalArgumentException
266-
* if the topic name or filter is invalid
267-
*/
268-
public static boolean isMatched(String topicFilter, String topicName) throws IllegalArgumentException {
269-
int topicPos = 0;
270-
int filterPos = 0;
271-
int topicLen = topicName.length();
272-
int filterLen = topicFilter.length();
273-
274-
MqttTopic.validate(topicFilter, true);
275-
MqttTopic.validate(topicName, false);
276-
277-
if (topicFilter.equals(topicName)) {
278-
return true;
279-
}
280-
281-
while (filterPos < filterLen && topicPos < topicLen) {
282-
if (topicName.charAt(topicPos) == '/' && topicFilter.charAt(filterPos) != '/')
283-
284-
break;
285-
if (topicFilter.charAt(filterPos) != '+' && topicFilter.charAt(filterPos) != '#'
286-
&& topicFilter.charAt(filterPos) != topicName.charAt(topicPos))
287-
break;
288-
if (topicFilter.charAt(filterPos) == '+') { // skip until we meet the next separator, or end of string
289-
int nextpos = topicPos + 1;
290-
while (nextpos < topicLen && topicName.charAt(nextpos) != '/')
291-
nextpos = ++topicPos + 1;
292-
} else if (topicFilter.charAt(filterPos) == '#')
293-
topicPos = topicLen - 1; // skip until end of string
294-
filterPos++;
295-
topicPos++;
296-
}
297-
298-
if ((topicPos == topicLen) && (filterPos == filterLen)) {
299-
return true;
300-
} else {
301-
/*
302-
* https://github.com/eclipse/paho.mqtt.java/issues/418
303-
* Covers edge case to match sport/# to sport
304-
*/
305-
if ((topicFilter.length() - topicName.length()) == 2 &&
306-
topicFilter.substring(topicFilter.length() -2, topicFilter.length()).equals("/#")) {
307-
String filterSub = topicFilter.substring(0, topicFilter.length() - 2);
308-
if (filterSub.equals(topicName)) {
309-
System.err.println("filterSub equals topicName: " + filterSub + " == " + topicName);
310-
return true;
311-
}
312-
}
313-
}
314-
return false;
315-
}
316-
317125
}

org.eclipse.paho.mqttv5.client/src/main/java/org/eclipse/paho/mqttv5/client/internal/ClientState.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,7 @@ protected void notifyReceivedAck(MqttAck ack) throws MqttException {
11151115
* @param ack
11161116
* - The Orphaned Ack
11171117
* @throws MqttException
1118+
* if an exception occurs whilst handling orphaned Acks
11181119
*/
11191120
protected void handleOrphanedAcks(MqttAck ack) throws MqttException {
11201121
final String methodName = "handleOrphanedAcks";
@@ -1161,7 +1162,7 @@ protected void handleInboundPubRel(MqttPubRel pubRel) throws MqttException {
11611162
MqttPubComp pubComp = new MqttPubComp(MqttReturnCode.RETURN_CODE_SUCCESS, pubRel.getMessageId(),
11621163
new MqttProperties());
11631164
// @TRACE 668=Creating MqttPubComp: {0}
1164-
log.info(CLASS_NAME, methodName, "668", new Object[] { pubComp.toString()});
1165+
log.info(CLASS_NAME, methodName, "668", new Object[] { pubComp.toString() });
11651166
this.send(pubComp, null);
11661167
}
11671168
}

org.eclipse.paho.mqttv5.client/src/main/java/org/eclipse/paho/mqttv5/client/internal/CommsCallback.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.eclipse.paho.mqttv5.client.MqttDeliveryToken;
3333
import org.eclipse.paho.mqttv5.client.MqttDisconnectResponse;
3434
import org.eclipse.paho.mqttv5.client.MqttToken;
35-
import org.eclipse.paho.mqttv5.client.MqttTopic;
3635
import org.eclipse.paho.mqttv5.client.logging.Logger;
3736
import org.eclipse.paho.mqttv5.client.logging.LoggerFactory;
3837
import org.eclipse.paho.mqttv5.common.MqttException;
@@ -45,6 +44,7 @@
4544
import org.eclipse.paho.mqttv5.common.packet.MqttPublish;
4645
import org.eclipse.paho.mqttv5.common.packet.MqttReturnCode;
4746
import org.eclipse.paho.mqttv5.common.packet.UserProperty;
47+
import org.eclipse.paho.mqttv5.common.util.MqttTopicValidator;
4848

4949
/**
5050
* Bridge between Receiver and the external API. This class gets called by
@@ -596,7 +596,7 @@ protected boolean deliverMessage(String topicName, int messageId, MqttMessage aM
596596
if (aMessage.getProperties().getSubscriptionIdentifiers().isEmpty()) {
597597
// No Subscription IDs, use topic filter matching
598598
for (Map.Entry<String, Integer> entry : this.callbackTopicMap.entrySet()) {
599-
if (MqttTopic.isMatched(entry.getKey(), topicName)) {
599+
if (MqttTopicValidator.isMatched(entry.getKey(), topicName)) {
600600
aMessage.setId(messageId);
601601
this.callbackMap.get(entry.getValue()).messageArrived(topicName, aMessage);
602602
delivered = true;

org.eclipse.paho.mqttv5.client/src/test/java/org/eclipse/paho/mqttv5/client/test/StringValidationTest.java

Lines changed: 0 additions & 39 deletions
This file was deleted.

org.eclipse.paho.mqttv5.common/src/main/java/org/eclipse/paho/mqttv5/common/packet/MqttProperties.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.List;
1111

1212
import org.eclipse.paho.mqttv5.common.MqttException;
13+
import org.eclipse.paho.mqttv5.common.util.MqttTopicValidator;
1314

1415
/**
1516
* MQTT v5 Properties Class. This Class contains all of the available MQTTv5
@@ -1225,6 +1226,7 @@ public String getResponseTopic() {
12251226
* The Response Topic.
12261227
*/
12271228
public void setResponseTopic(String responseTopic) {
1229+
MqttTopicValidator.validate(responseTopic, false);
12281230
this.responseTopic = responseTopic;
12291231
}
12301232

0 commit comments

Comments
 (0)