Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Add user agent override list support. (fixes #488) #777

Merged
merged 1 commit into from
Nov 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package org.mozilla.vrbrowser.browser;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Rect;
Expand Down Expand Up @@ -76,6 +77,7 @@ public static SessionStore get() {
private LinkedList<SessionChangeListener> mSessionChangeListeners;
private LinkedList<GeckoSession.TextInputDelegate> mTextInputListeners;
private LinkedList<GeckoSession.PromptDelegate> mPromptListeners;
private UserAgentOverride mUserAgentOverride;

public interface SessionChangeListener {
void onNewSession(GeckoSession aSession, int aId);
Expand Down Expand Up @@ -175,6 +177,10 @@ public void setContext(Context aContext, Bundle aExtras) {

mContext = aContext;
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
if (mUserAgentOverride == null) {
mUserAgentOverride = new UserAgentOverride();
mUserAgentOverride.loadOverridesFromAssets((Activity)aContext, aContext.getString(R.string.user_agent_override_file));
}
}

public void dumpAllState(Integer sessionId) {
Expand Down Expand Up @@ -858,6 +864,7 @@ public void onCanGoForward(GeckoSession aSession, boolean aCanGoForward) {
@Override
public @Nullable GeckoResult<AllowOrDeny> onLoadRequest(@NonNull GeckoSession aSession, @NonNull LoadRequest aRequest) {
final GeckoResult<AllowOrDeny> result = new GeckoResult<>();
aSession.getSettings().setString(GeckoSessionSettings.USER_AGENT_OVERRIDE, mUserAgentOverride.lookupOverride(aRequest.uri));
if (PRIVATE_BROWSING_URI.equalsIgnoreCase(aRequest.uri)) {
switchPrivateMode();
result.complete(AllowOrDeny.ALLOW);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package org.mozilla.vrbrowser.browser;

import android.app.Activity;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

public class UserAgentOverride {
private final static String LOGTAG = "VRB";
private static final String NO_OVERRIDE_FOUND = "NO OVERRIDE USER AGENT FOUND";
private ArrayMap<String, String> mOverrideMap;
private ArrayMap<String, String> mOverrideCache;
public UserAgentOverride() {
mOverrideMap = new ArrayMap<>();
mOverrideCache = new ArrayMap<>();
}

public void loadOverridesFromAssets(Activity aActivity, String aFileName) {
String json = null;
try (InputStream is = aActivity.getAssets().open(aFileName)) {
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
bluemarvin marked this conversation as resolved.
Show resolved Hide resolved
json = new String(buffer, "UTF-8");
importJSONData(json);
} catch (IOException e) {
Log.e(LOGTAG, "Failed reading user agent override file: " + aFileName + " Error: " + e.getMessage());
}
}

public String lookupOverride(final String aUri) {
Log.d(LOGTAG, "lookupOverride for: " + aUri);
URI uri = URI.create(aUri);
String fullDomain = uri.getHost();
if (fullDomain == null) {
return null;
}

fullDomain = fullDomain.toLowerCase();

String override = mOverrideCache.get(fullDomain);

if (override != null) {
Log.d(LOGTAG, "Found override: " + override);
return override.equals(NO_OVERRIDE_FOUND) ? null : override;
}

List<String> domains = Arrays.asList(fullDomain.split("\\."));
final int domainCount = domains.size();
String[] checkedDomains = new String[domainCount];

for (int ix = 0; ix < domainCount; ix++) {
String domain = TextUtils.join(".", domains.subList(ix, domainCount));
checkedDomains[ix] = domain;
override = mOverrideCache.get(domain);
if (override != null) {
Log.d(LOGTAG, "found cached override: " + override);
addToCache(checkedDomains, override);
return override.equals(NO_OVERRIDE_FOUND) ? null : override;
}
String domainHash = hashDomain(domain);
if (domainHash == null) {
Log.d(LOGTAG, "Failed to hash domain: " + domain);
return null;
}
Log.d(LOGTAG, "hash: " + domainHash);
override = mOverrideMap.get(domainHash);
if (override != null) {
Log.d(LOGTAG, "found override from hash: " + override);
addToCache(checkedDomains, override);
return override;
}
}
addToCache(checkedDomains, NO_OVERRIDE_FOUND);
return null;
}

private void addToCache(String[] aDomains, String aOverride) {
for (String domain: aDomains) {
if (domain == null) {
Log.d(LOGTAG, "Found null domain in checked list");
continue;
} else {
Log.d(LOGTAG, domain + " override: " + aOverride);
}
mOverrideCache.put(domain, aOverride);
}
}

private String hashDomain(String aDomain) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-512");
byte[] digest = md.digest(aDomain.getBytes());
StringBuilder sb = new StringBuilder();
for (int value: digest) {
sb.append(Integer.toString((value & 0xff) + 0x100, 16).substring(1));
}

return sb.toString();
} catch (NoSuchAlgorithmException e) {
Log.e(LOGTAG, "Error while trying to hash domain: " + e.getMessage());
}
return null;
}

private void importJSONData(final String aData) {
try {
JSONObject json = new JSONObject(aData);
Iterator<String> iter = json.keys();
while (iter.hasNext()) {
String key = iter.next();
try {
String value = json.getString(key);
Log.d(LOGTAG, "User agent override: " + key + " -> " + value);
mOverrideMap.put(key, value);
} catch (JSONException e) {
Log.e(LOGTAG, "Failed to find UA Override while parsing file for key: " + key);
}
}

} catch (JSONException e) {
Log.e(LOGTAG, "Failed to import user agent override JSON data: " + e.getMessage());
}
}
}
5 changes: 5 additions & 0 deletions app/src/main/assets/userAgentOverride.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"ae0755740e4354ac67025056e775ad06d8a529ae4f37244fbb02d72199e2c780311e47aa9895079b980ec4bfa676f1f39c4ab41ea995c524e52bde9a73623da2": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12) AppleWebKit/602.1.21 (KHTML, like Gecko) Version/9.2 Safari/602.1.21",
"e6137b4c2f49a3917c2c90a50fb270a5eebb962f2c72344ae2e29e321bb21891e5ca4fec06cae78e14f4a8510473e934234e9ec3f60e8415f5f6da754c55b9b1": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12) AppleWebKit/602.1.21 (KHTML, like Gecko) Version/9.2 Safari/602.1.21",
"a5593b10e239d568ecb4a1f5f9980cc00da2d6b14ef6696760481eeda8933811a96a7a5ecc5d8eebc40c427ae9aeda9025925db07217e375875f0fb1297216a5": "Mozilla/5.0 (X11; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0 SamsungBrowser"
}
1 change: 1 addition & 0 deletions app/src/main/res/values/non_L10n.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<string name="app_permission_name" translatable="false">org.mozilla.vrbrowser.CRASH_RECEIVER_PERMISSION</string>
<string name="developer_options_by" translatable="false">x</string>
<string name="crash_app_name" translatable="false">FirefoxReality</string>
<string name="user_agent_override_file" translatable="false">userAgentOverride.json</string>

<!-- SEARCH ENGINES -->
<string name="geolocation_api_url" translatable="false">https://location.services.mozilla.com/v1/country</string>
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
ext.geckoNightly = [
// GeckoView versions can be found here:
// https://maven.mozilla.org/?prefix=maven2/org/mozilla/geckoview/
version: '65.0.20181110100141'
version: '65.0.20181119100115'
]

// Android components version
Expand Down