Restrict user-agent version matching to a more confined space.
This commit is contained in:
parent
f3b644ceb8
commit
40684a93a2
|
@ -5,6 +5,7 @@ import org.whispersystems.textsecuregcm.util.Pair;
|
|||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -15,66 +16,38 @@ import java.util.regex.Pattern;
|
|||
*/
|
||||
public class UserAgentTagUtil {
|
||||
|
||||
static final int MAX_VERSIONS = 10_000;
|
||||
public static final String PLATFORM_TAG = "platform";
|
||||
public static final String VERSION_TAG = "clientVersion";
|
||||
static final List<Tag> OVERFLOW_TAGS = List.of(Tag.of(PLATFORM_TAG, "overflow"), Tag.of(VERSION_TAG, "overflow"));
|
||||
static final List<Tag> UNRECOGNIZED_TAGS = List.of(Tag.of(PLATFORM_TAG, "unrecognized"), Tag.of(VERSION_TAG, "unrecognized"));
|
||||
|
||||
public static final String PLATFORM_TAG = "platform";
|
||||
public static final String VERSION_TAG = "clientVersion";
|
||||
private static final Map<String, Pattern> PATTERNS_BY_PLATFORM = Map.of(
|
||||
"android", Pattern.compile("^Signal-Android (4[^ ]+).*$", Pattern.CASE_INSENSITIVE),
|
||||
"desktop", Pattern.compile("^Signal Desktop (1[^ ]+).*$", Pattern.CASE_INSENSITIVE),
|
||||
"ios", Pattern.compile("^Signal/(3[^ ]+) \\(.*ios.*\\)$", Pattern.CASE_INSENSITIVE));
|
||||
|
||||
static final List<Tag> OVERFLOW_TAGS = List.of(Tag.of(PLATFORM_TAG, "overflow"), Tag.of(VERSION_TAG, "overflow"));
|
||||
static final List<Tag> UNRECOGNIZED_TAGS = List.of(Tag.of(PLATFORM_TAG, "unrecognized"), Tag.of(VERSION_TAG, "unrecognized"));
|
||||
|
||||
private static final Pattern USER_AGENT_PATTERN = Pattern.compile("^Signal[ \\-]([^ ]+) ([^ ]+).*$", Pattern.CASE_INSENSITIVE);
|
||||
private static final Pattern IOS_USER_AGENT_PATTERN = Pattern.compile("^Signal/([^ ]+) \\(.*ios.*\\)$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static final Set<Pair<String, String>> SEEN_VERSIONS = new HashSet<>();
|
||||
static final int MAX_VERSIONS = 10_000;
|
||||
private static final Set<Pair<String, String>> SEEN_VERSIONS = new HashSet<>();
|
||||
|
||||
private UserAgentTagUtil() {
|
||||
}
|
||||
|
||||
public static List<Tag> getUserAgentTags(final String userAgent) {
|
||||
final List<Tag> tags;
|
||||
if (userAgent != null) {
|
||||
for (final Map.Entry<String, Pattern> entry : PATTERNS_BY_PLATFORM.entrySet()) {
|
||||
final String platform = entry.getKey();
|
||||
final Pattern pattern = entry.getValue();
|
||||
final Matcher matcher = pattern.matcher(userAgent);
|
||||
|
||||
if (userAgent == null) {
|
||||
tags = UNRECOGNIZED_TAGS;
|
||||
} else {
|
||||
tags = getAndroidOrDesktopUserAgentTags(userAgent)
|
||||
.orElseGet(() -> getIOSUserAgentTags(userAgent)
|
||||
.orElse(UNRECOGNIZED_TAGS));
|
||||
if (matcher.matches()) {
|
||||
final String version = matcher.group(1);
|
||||
|
||||
return allowVersion(platform, version) ? List.of(Tag.of(PLATFORM_TAG, platform), Tag.of(VERSION_TAG, version)) : OVERFLOW_TAGS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
private static Optional<List<Tag>> getAndroidOrDesktopUserAgentTags(final String userAgent) {
|
||||
final Matcher matcher = USER_AGENT_PATTERN.matcher(userAgent);
|
||||
final Optional<List<Tag>> maybeTags;
|
||||
|
||||
if (matcher.matches()) {
|
||||
final String platform = matcher.group(1).toLowerCase();
|
||||
final String version = matcher.group(2);
|
||||
|
||||
maybeTags = Optional.of(allowVersion(platform, version) ? List.of(Tag.of(PLATFORM_TAG, platform), Tag.of(VERSION_TAG, version)) : OVERFLOW_TAGS);
|
||||
} else {
|
||||
maybeTags = Optional.empty();
|
||||
}
|
||||
|
||||
return maybeTags;
|
||||
}
|
||||
|
||||
private static Optional<List<Tag>> getIOSUserAgentTags(final String userAgent) {
|
||||
final Matcher matcher = IOS_USER_AGENT_PATTERN.matcher(userAgent);
|
||||
final Optional<List<Tag>> maybeTags;
|
||||
|
||||
if (matcher.matches()) {
|
||||
final String platform = "ios";
|
||||
final String version = matcher.group(1);
|
||||
|
||||
maybeTags = Optional.of(allowVersion(platform, version) ? List.of(Tag.of(PLATFORM_TAG, platform), Tag.of(VERSION_TAG, version)) : OVERFLOW_TAGS);
|
||||
} else {
|
||||
maybeTags = Optional.empty();
|
||||
}
|
||||
|
||||
return maybeTags;
|
||||
return UNRECOGNIZED_TAGS;
|
||||
}
|
||||
|
||||
private static boolean allowVersion(final String platform, final String version) {
|
||||
|
|
|
@ -1,59 +1,54 @@
|
|||
package org.whispersystems.textsecuregcm.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import junitparams.JUnitParamsRunner;
|
||||
import junitparams.Parameters;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(JUnitParamsRunner.class)
|
||||
public class UserAgentTagUtilTest {
|
||||
|
||||
@Test
|
||||
public void testGetUserAgentTags() {
|
||||
assertEquals(UserAgentTagUtil.UNRECOGNIZED_TAGS,
|
||||
UserAgentTagUtil.getUserAgentTags("This is obviously not a reasonable User-Agent string."));
|
||||
@Parameters(method = "argumentsForTestGetUserAgentTags")
|
||||
public void testGetUserAgentTags(final String userAgent, final List<Tag> expectedTags) {
|
||||
assertEquals(new HashSet<>(expectedTags),
|
||||
new HashSet<>(UserAgentTagUtil.getUserAgentTags(userAgent)));
|
||||
}
|
||||
|
||||
assertEquals(UserAgentTagUtil.UNRECOGNIZED_TAGS, UserAgentTagUtil.getUserAgentTags(null));
|
||||
|
||||
{
|
||||
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal-Android 4.53.7 (Android 8.1)");
|
||||
|
||||
assertEquals(2, tags.size());
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7")));
|
||||
}
|
||||
|
||||
{
|
||||
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal Desktop 1.2.3");
|
||||
|
||||
assertEquals(2, tags.size());
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop")));
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "1.2.3")));
|
||||
}
|
||||
|
||||
{
|
||||
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)");
|
||||
|
||||
assertEquals(2, tags.size());
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "ios")));
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "3.9.0")));
|
||||
}
|
||||
@SuppressWarnings("unused")
|
||||
private Object argumentsForTestGetUserAgentTags() {
|
||||
return new Object[] {
|
||||
new Object[] { "This is obviously not a reasonable User-Agent string.", UserAgentTagUtil.UNRECOGNIZED_TAGS },
|
||||
new Object[] { null, UserAgentTagUtil.UNRECOGNIZED_TAGS },
|
||||
new Object[] { "Signal-Android 4.53.7 (Android 8.1)", List.of(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android"), Tag.of(UserAgentTagUtil.VERSION_TAG, "4.53.7")) },
|
||||
new Object[] { "Signal Desktop 1.2.3", List.of(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "desktop"), Tag.of(UserAgentTagUtil.VERSION_TAG, "1.2.3")) },
|
||||
new Object[] { "Signal/3.9.0 (iPhone; iOS 12.2; Scale/3.00)", List.of(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "ios"), Tag.of(UserAgentTagUtil.VERSION_TAG, "3.9.0")) },
|
||||
new Object[] { "Signal-Android 1.2.3 (Android 8.1)", UserAgentTagUtil.UNRECOGNIZED_TAGS },
|
||||
new Object[] { "Signal Desktop 3.9.0", UserAgentTagUtil.UNRECOGNIZED_TAGS },
|
||||
new Object[] { "Signal/4.53.7 (iPhone; iOS 12.2; Scale/3.00)", UserAgentTagUtil.UNRECOGNIZED_TAGS },
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserAgentTagsFlooded() {
|
||||
for (int i = 0; i < UserAgentTagUtil.MAX_VERSIONS; i++) {
|
||||
UserAgentTagUtil.getUserAgentTags(String.format("Signal-Android 1.0.%d (Android 8.1)", i));
|
||||
UserAgentTagUtil.getUserAgentTags(String.format("Signal-Android 4.0.%d (Android 8.1)", i));
|
||||
}
|
||||
|
||||
assertEquals(UserAgentTagUtil.OVERFLOW_TAGS,
|
||||
UserAgentTagUtil.getUserAgentTags("Signal-Android 2.0.0 (Android 8.1)"));
|
||||
UserAgentTagUtil.getUserAgentTags("Signal-Android 4.1.0 (Android 8.1)"));
|
||||
|
||||
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal-Android 1.0.0 (Android 8.1)");
|
||||
final List<Tag> tags = UserAgentTagUtil.getUserAgentTags("Signal-Android 4.0.0 (Android 8.1)");
|
||||
|
||||
assertEquals(2, tags.size());
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.PLATFORM_TAG, "android")));
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "1.0.0")));
|
||||
assertTrue(tags.contains(Tag.of(UserAgentTagUtil.VERSION_TAG, "4.0.0")));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue