Skip to content

Commit

Permalink
fix: browser parsing
Browse files Browse the repository at this point in the history
Added new opera userAgent string.
Better logging for failures.
Better matching for version string.
Fixed android mistaken check.

Part of #20610
  • Loading branch information
caalador committed Dec 10, 2024
1 parent 4c6524d commit 951709d
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.io.Serializable;
import java.util.Locale;

import org.slf4j.LoggerFactory;

import com.vaadin.flow.shared.BrowserDetails;

/**
Expand Down Expand Up @@ -65,7 +67,13 @@ public class WebBrowser implements Serializable {

if (agent != null) {
browserApplication = agent;
browserDetails = new BrowserDetails(agent);
browserDetails = new BrowserDetails(agent) {
@Override
protected void log(String error, Exception e) {
LoggerFactory.getLogger(BrowserDetails.class).error(error,
e);
}
};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,17 @@ public BrowserDetails(String userAgent) {
isWebKit = !isTrident && userAgent.contains("applewebkit");

// browser name
isChrome = userAgent.contains(CHROME) || userAgent.contains(" crios/")
|| userAgent.contains(HEADLESSCHROME);
isOpera = userAgent.contains("opera");
isChrome = (userAgent.contains(CHROME) || userAgent.contains(" crios/")
|| userAgent.contains(HEADLESSCHROME))
&& !userAgent.contains(" opr/");
isOpera = userAgent.contains("opera") || userAgent.contains(" opr/");
isIE = userAgent.contains("msie") && !isOpera
&& !userAgent.contains("webtv");
// IE 11 no longer contains MSIE in the user agent
isIE = isIE || isTrident;

isSafari = !isChrome && !isIE && userAgent.contains("safari");
isSafari = !isChrome && !isIE && !isOpera
&& userAgent.contains("safari");
isFirefox = userAgent.contains(" firefox/");
if (userAgent.contains(" edge/") || userAgent.contains(" edg/")
|| userAgent.contains(" edga/")
Expand Down Expand Up @@ -148,7 +150,7 @@ public BrowserDetails(String userAgent) {
if (rvPos >= 0) {
String tmp = userAgent.substring(rvPos + 3);
tmp = tmp.replaceFirst("(\\.[0-9]+).+", "$1");
parseVersionString(tmp);
parseVersionString(tmp, userAgent);
}
} else if (isTrident) {
// potentially IE 11 in compatibility mode
Expand All @@ -161,18 +163,24 @@ public BrowserDetails(String userAgent) {
.substring(userAgent.indexOf("msie ") + 5);
ieVersionString = safeSubstring(ieVersionString, 0,
ieVersionString.indexOf(';'));
parseVersionString(ieVersionString);
parseVersionString(ieVersionString, userAgent);
}
} else if (isFirefox) {
int i = userAgent.indexOf(" firefox/") + 9;
parseVersionString(safeSubstring(userAgent, i, i + 5));
parseVersionString(
safeSubstring(userAgent, i,
i + getVersionStringLength(userAgent, i)),
userAgent);
} else if (isChrome) {
parseChromeVersion(userAgent);
} else if (isSafari) {
int i = userAgent.indexOf(" version/");
if (i >= 0) {
i += 9;
parseVersionString(safeSubstring(userAgent, i, i + 5));
parseVersionString(
safeSubstring(userAgent, i,
i + getVersionStringLength(userAgent, i)),
userAgent);
} else {
int engineVersion = (int) (browserEngineVersion * 10);
if (engineVersion >= 6010 && engineVersion < 6015) {
Expand Down Expand Up @@ -206,10 +214,15 @@ public BrowserDetails(String userAgent) {
if (i != -1) {
// Version present in Opera 10 and newer
i += 9; // " version/".length
} else if (userAgent.contains(" opr/")) {
i = userAgent.indexOf(" opr/") + 5;
} else {
i = userAgent.indexOf("opera/") + 6;
}
parseVersionString(safeSubstring(userAgent, i, i + 5));
parseVersionString(
safeSubstring(userAgent, i,
i + getVersionStringLength(userAgent, i)),
userAgent);
} else if (isEdge) {
int i = userAgent.indexOf(" edge/") + 6;
if (userAgent.contains(" edg/")) {
Expand All @@ -220,7 +233,10 @@ public BrowserDetails(String userAgent) {
i = userAgent.indexOf(" edgios/") + 8;
}

parseVersionString(safeSubstring(userAgent, i, i + 8));
parseVersionString(
safeSubstring(userAgent, i,
i + getVersionStringLength(userAgent, i)),
userAgent);
}
} catch (Exception e) {
// Browser version parsing failed
Expand Down Expand Up @@ -274,16 +290,16 @@ private void parseChromeOSVersion(String userAgent) {
}
String osVersionString = userAgent.substring(cur + 1, end);
String[] parts = osVersionString.split("\\.");
parseChromeOsVersionParts(parts);
parseChromeOsVersionParts(parts, userAgent);
}

private void parseChromeOsVersionParts(String[] parts) {
private void parseChromeOsVersionParts(String[] parts, String userAgent) {
osMajorVersion = -1;
osMinorVersion = -1;

if (parts.length > 2) {
osMajorVersion = parseVersionPart(parts[0], OS_MAJOR);
osMinorVersion = parseVersionPart(parts[1], OS_MINOR);
osMajorVersion = parseVersionPart(parts[0], OS_MAJOR, userAgent);
osMinorVersion = parseVersionPart(parts[1], OS_MINOR, userAgent);
}
}

Expand All @@ -298,11 +314,13 @@ private void parseChromeVersion(String userAgent) {
i += CHROME.length();
}
int versionBreak = getVersionStringLength(userAgent, i);
parseVersionString(safeSubstring(userAgent, i, i + versionBreak));
parseVersionString(safeSubstring(userAgent, i, i + versionBreak),
userAgent);
} else {
i += crios.length(); // move index to version string start
int versionBreak = getVersionStringLength(userAgent, i);
parseVersionString(safeSubstring(userAgent, i, i + versionBreak));
parseVersionString(safeSubstring(userAgent, i, i + versionBreak),
userAgent);
}
}

Expand All @@ -327,7 +345,7 @@ private static int getVersionStringLength(String userAgent,

private void parseAndroidVersion(String userAgent) {
// Android 5.1;
if (!userAgent.contains("android")) {
if (!userAgent.contains("android ")) {
return;
}

Expand All @@ -337,7 +355,7 @@ private void parseAndroidVersion(String userAgent) {
osVersionString = safeSubstring(osVersionString, 0,
osVersionString.indexOf(";"));
String[] parts = osVersionString.split("\\.");
parseOsVersion(parts);
parseOsVersion(parts, userAgent);
}

private void parseIOSVersion(String userAgent) {
Expand All @@ -349,35 +367,43 @@ private void parseIOSVersion(String userAgent) {
String osVersionString = safeSubstring(userAgent,
userAgent.indexOf("os ") + 3, userAgent.indexOf(" like mac"));
String[] parts = osVersionString.split("_");
parseOsVersion(parts);
parseOsVersion(parts, userAgent);
}

private void parseOsVersion(String[] parts) {
private void parseOsVersion(String[] parts, String userAgent) {
osMajorVersion = -1;
osMinorVersion = -1;

if (parts.length >= 1) {
osMajorVersion = parseVersionPart(parts[0], OS_MAJOR);
osMajorVersion = parseVersionPart(parts[0], OS_MAJOR, userAgent);
}
if (parts.length >= 2) {
// Some Androids report version numbers as "2.1-update1"
int dashIndex = parts[1].indexOf('-');
if (dashIndex > -1) {
String dashlessVersion = parts[1].substring(0, dashIndex);
osMinorVersion = parseVersionPart(dashlessVersion, OS_MINOR);
osMinorVersion = parseVersionPart(dashlessVersion, OS_MINOR,
userAgent);
} else {
osMinorVersion = parseVersionPart(parts[1], OS_MINOR);
osMinorVersion = parseVersionPart(parts[1], OS_MINOR,
userAgent);
}
}
}

private void parseVersionString(String versionString) {
private void parseVersionString(String versionString, String userAgent) {
int idx = versionString.indexOf('.');
if (idx < 0) {
idx = versionString.length();
}
String majorVersionPart = safeSubstring(versionString, 0, idx);
browserMajorVersion = parseVersionPart(majorVersionPart, BROWSER_MAJOR);
browserMajorVersion = parseVersionPart(majorVersionPart, BROWSER_MAJOR,
userAgent);

if (browserMajorVersion == -1) {
// no need to scan for minor if major version scanning failed.
return;
}

int idx2 = versionString.indexOf('.', idx + 1);
if (idx2 < 0) {
Expand All @@ -390,7 +416,8 @@ private void parseVersionString(String versionString) {
}
String minorVersionPart = safeSubstring(versionString, idx + 1, idx2)
.replaceAll("[^0-9].*", "");
browserMinorVersion = parseVersionPart(minorVersionPart, BROWSER_MINOR);
browserMinorVersion = parseVersionPart(minorVersionPart, BROWSER_MINOR,
userAgent);
}

private static String safeSubstring(String string, int beginIndex,
Expand All @@ -410,11 +437,13 @@ private static String safeSubstring(String string, int beginIndex,
return string.substring(trimmedStart, trimmedEnd);
}

private int parseVersionPart(String versionString, String partName) {
private int parseVersionPart(String versionString, String partName,
String userAgent) {
try {
return Integer.parseInt(versionString);
} catch (Exception e) {
log(partName + " version parsing failed for: " + versionString, e);
log(partName + " version parsing failed for: " + versionString
+ "\nWith userAgent: " + userAgent, e);
}
return -1;
}
Expand Down Expand Up @@ -667,10 +696,10 @@ && getOperatingSystemMinorVersion() >= 7))) {
return false;
}

private static void log(String error, Exception e) {
protected void log(String error, Exception e) {
// "Logs" to stdout so the problem can be found but does not prevent
// using the app. As this class is shared, we do not use
// java.util.logging
// slf4j for logging as normal.
System.err.println(error + ' ' + e.getMessage());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,12 @@
*/
package com.vaadin.flow.server;

import java.util.Locale;

import jdk.jfr.ValueDescriptor;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;

public class WebBrowserTest {

Expand Down Expand Up @@ -56,4 +60,53 @@ public void isIPhone_noDetails_returnsFalse() {
public void isChromeOS_noDetails_returnsFalse() {
Assert.assertFalse(browser.isChromeOS());
}

@Test
public void isSafariOnMac_userDetails_returnsTrue() {
VaadinRequest request = initRequest(
"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_6_2) AppleWebKit/611.3.10.1.5 (KHTML, like Gecko) Version/14.1.2 Safari/611.3.10.1.5");

browser = new WebBrowser(request);
Assert.assertTrue(browser.isSafari());
Assert.assertTrue(browser.isMacOSX());
}

@Test
public void isChromeOnWindows_userDetails_returnsTrue() {
VaadinRequest request = initRequest(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36");

browser = new WebBrowser(request);
Assert.assertTrue(browser.isChrome());
Assert.assertTrue(browser.isWindows());
}

@Test
public void isOperaOnWindows_userDetails_returnsTrue() {
VaadinRequest request = initRequest(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0");

browser = new WebBrowser(request);
Assert.assertTrue(browser.isOpera());
Assert.assertTrue(browser.isWindows());
}

@Test
public void isFirefoxOnAndroid_userDetails_returnsTrue() {
VaadinRequest request = initRequest(
"Mozilla/5.0 (Android; Tablet; rv:33.0) Gecko/33.0 Firefox/33.0");

browser = new WebBrowser(request);
Assert.assertTrue(browser.isFirefox());
Assert.assertTrue(browser.isAndroid());
}

private static VaadinRequest initRequest(String userAgent) {
VaadinRequest request = Mockito.mock(VaadinRequest.class);
Mockito.when(request.getLocale()).thenReturn(Locale.ENGLISH);
Mockito.when(request.getRemoteAddr()).thenReturn("0.0.0.0");
Mockito.when(request.isSecure()).thenReturn(false);
Mockito.when(request.getHeader("User-Agent")).thenReturn(userAgent);
return request;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class BrowserDetailsTest extends TestCase {
private static final String OPERA964_WINDOWS = "Opera/9.64(Windows NT 5.1; U; en) Presto/2.1.1";
private static final String OPERA1010_WINDOWS = "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.2.15 Version/10.10";
private static final String OPERA1050_WINDOWS = "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.5.22 Version/10.50";
private static final String OPERA115_WINDOWS = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 OPR/115.0.0.0";

private static final String CHROME3_MAC = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/3.0.198 Safari/532.0";
private static final String CHROME4_WINDOWS = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.89 Safari/532.5";
Expand Down Expand Up @@ -690,7 +691,17 @@ public void testHeadlessChrome() {
assertBrowserMinorVersion(bd, 0);
assertEngineVersion(bd, 537.36f);
assertLinux(bd);
}

public void testOpera65() {
String userAgent = OPERA115_WINDOWS;
BrowserDetails bd = new BrowserDetails(userAgent);
assertWebKit(bd);
assertOpera(bd);
assertBrowserMajorVersion(bd, 115);
assertBrowserMinorVersion(bd, 0);
assertEngineVersion(bd, 537.36f);
assertWindows(bd);
}

public void testIos11FacebookBrowser() {
Expand Down

0 comments on commit 951709d

Please sign in to comment.