Permalink
Browse files

Add custom UA (#55)

This produces strings like:
Mozilla/5.0 (Linux; Android 6.0.1) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Focus/1.0  Chrome/56.0.2924.87 Mobile Safari/537.36
General format:
Mozilla/5.0 (Linux; Android <Android Version>) AppleWebKit/<WebKit Rev> (KHTML, like Gecko) Version/4.0 Focus/<Focus Rev>  Chrome/<Chrome Rev> Mobile Safari/<WebKit Rev>
  • Loading branch information...
ahunt committed Mar 17, 2017
1 parent fbcfc9a commit aa0e8058c5c70e3395b2e2d3411b6b1ab0a17ea7
@@ -5,4 +5,6 @@
<resources>
<string name="app_name" translatable="false">Firefox Klar</string>
<string name="launcher_name" translatable="false">Klar</string>

<string name="useragent_appname" translatable="false">Klar</string>
</resources>
@@ -5,4 +5,7 @@
<resources>
<string name="app_name" translatable="false">Firefox Focus</string>
<string name="launcher_name" translatable="false">Focus</string>

<!-- This String is used in the User Agent -->
<string name="useragent_appname" translatable="false">Focus</string>
</resources>
@@ -0,0 +1,43 @@
package org.mozilla.focus.web;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

import static org.junit.Assert.*;

// Use roboelectric mocking because we're using android.text.TextUtils
@RunWith(RobolectricTestRunner.class)
public class WebViewProviderTest {

@Test public void testGetUABrowserString() {
// Typical situation with a webview UA string from Android 5:
String focusToken = "Focus/1.0";
final String existing = "Mozilla/5.0 (Linux; Android 5.0.2; Android SDK built for x86_64 Build/LSY66K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile Safari/537.36";
assertEquals("AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 " + focusToken +" Chrome/37.0.0.0 Mobile Safari/537.36",
WebViewProvider.getUABrowserString(existing, focusToken));

// Make sure we can use any token, e.g Klar:
focusToken = "Klar/2.0";
assertEquals("AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 " + focusToken +" Chrome/37.0.0.0 Mobile Safari/537.36",
WebViewProvider.getUABrowserString(existing, focusToken));

// And a non-standard UA String, which doesn't contain AppleWebKit
focusToken = "Focus/1.0";
final String imaginaryKit = "Mozilla/5.0 (Linux) ImaginaryKit/-10 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile Safari/537.36";
assertEquals("ImaginaryKit/-10 (KHTML, like Gecko) Version/4.0 " + focusToken + " Chrome/37.0.0.0 Mobile Safari/537.36",
WebViewProvider.getUABrowserString(imaginaryKit, focusToken));

// Another non-standard UA String, this time with no Chrome (in which case we should be appending focus)
final String chromeless = "Mozilla/5.0 (Linux; Android 5.0.2; Android SDK built for x86_64 Build/LSY66K) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Imaginary/37.0.0.0 Mobile Safari/537.36";
assertEquals("AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Imaginary/37.0.0.0 Mobile Safari/537.36" + focusToken,
WebViewProvider.getUABrowserString(chromeless, focusToken));

// No AppleWebkit, no Chrome
final String chromelessImaginaryKit = "Mozilla/5.0 (Linux) ImaginaryKit/-10 (KHTML, like Gecko) Version/4.0 Imaginary/37.0.0.0 Mobile Safari/537.36";
assertEquals("ImaginaryKit/-10 (KHTML, like Gecko) Version/4.0 Imaginary/37.0.0.0 Mobile Safari/537.36" + focusToken,
WebViewProvider.getUABrowserString(chromelessImaginaryKit, focusToken));

}

}
@@ -7,9 +7,11 @@

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.preference.PreferenceManager;
import android.os.Build;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.webkit.CookieManager;
@@ -20,6 +22,8 @@
import android.webkit.WebViewDatabase;

import org.mozilla.focus.BuildConfig;
import org.mozilla.focus.R;

import org.mozilla.focus.utils.Settings;
import org.mozilla.focus.webkit.NestedWebView;
import org.mozilla.focus.webkit.TrackingProtectionWebViewClient;
@@ -68,6 +72,73 @@ private static void configureSettings(Context context, WebSettings settings) {
settings.setAllowFileAccess(false);

settings.setBlockNetworkImage(appSettings.shouldBlockImages());

settings.setUserAgentString(buildUserAgentString(context, settings));
}

/**
* Build the browser specific portion of the UA String, based on the webview's existing UA String.
*/
@VisibleForTesting static String getUABrowserString(final String existingUAString, final String focusToken) {
// Use the default WebView agent string here for everything after the platform, but insert
// Focus in front of Chrome.
// E.g. a default webview UA string might be:
// Mozilla/5.0 (Linux; Android 7.1.1; Pixel XL Build/NOF26V; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/56.0.2924.87 Mobile Safari/537.36
// And we reuse everything from AppleWebKit onwards, except for adding Focus.
int start = existingUAString.indexOf("AppleWebKit");
if (start == -1) {
// I don't know if any devices don't include AppleWebKit, but given the diversity of Android
// devices we should have a fallback: we search for the end of the platform String, and
// treat the next token as the start:
start = existingUAString.indexOf(")") + 2;

// If this was located at the very end, then there's nothing we can do, so let's just
// return focus:
if (start >= existingUAString.length()) {
return focusToken;
}
}

final String[] tokens = existingUAString.substring(start).split(" ");

for (int i = 0; i < tokens.length; i++) {
if (tokens[i].startsWith("Chrome")) {
tokens[i] = focusToken + " " + tokens[i];

return TextUtils.join(" ", tokens);
}
}

// If we didn't find a chrome token, we just append the focus token at the end:
return TextUtils.join(" ", tokens) + focusToken;
}

private static String buildUserAgentString(final Context context, final WebSettings settings) {
final StringBuilder uaBuilder = new StringBuilder();

uaBuilder.append("Mozilla/5.0");

// WebView by default includes "; wv" as part of the platform string, but we're a full browser
// so we shouldn't include that.
// Most webview based browsers (and chrome), include the device name AND build ID, e.g.
// "Pixel XL Build/NOF26V", that seems unnecessary (and not great from a privacy perspective),
// so we skip that too.
uaBuilder.append(" (Linux; Android ").append(Build.VERSION.RELEASE).append(") ");

final String existingWebViewUA = settings.getUserAgentString();

final String appVersion;
try {
appVersion = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
// This should be impossible - we should always be able to get information about ourselves:
throw new IllegalStateException("Unable find package details for Focus", e);
}

final String focusToken = context.getResources().getString(R.string.useragent_appname) + "/" + appVersion;
uaBuilder.append(getUABrowserString(existingWebViewUA, focusToken));

return uaBuilder.toString();
}

private static class WebkitView extends NestedWebView implements IWebView {

0 comments on commit aa0e805

Please sign in to comment.