From f70d889c03fba8f86c67a0bc2963145d2687fb99 Mon Sep 17 00:00:00 2001 From: n8fr8 Date: Mon, 2 Jun 2014 10:56:20 -0400 Subject: [PATCH] initial support for preference based proxy settings and filtering to stop HTML5 media leakage --- .../browser/lightning/BrowserActivity.java | 28 ++- .../browser/lightning/IncognitoActivity.java | 2 +- src/acr/browser/lightning/LightningView.java | 178 ++++++++++++++++-- .../lightning/PreferenceConstants.java | 4 + .../lightning/ReplacingInputStream.java | 91 +++++++++ 5 files changed, 284 insertions(+), 19 deletions(-) create mode 100644 src/acr/browser/lightning/ReplacingInputStream.java diff --git a/src/acr/browser/lightning/BrowserActivity.java b/src/acr/browser/lightning/BrowserActivity.java index 8d418d8..06865d8 100644 --- a/src/acr/browser/lightning/BrowserActivity.java +++ b/src/acr/browser/lightning/BrowserActivity.java @@ -449,15 +449,20 @@ public class BrowserActivity extends Activity implements BrowserController { getDir("icons", MODE_PRIVATE).getPath()); } - - checkForTor (); + boolean useProxy = mPreferences.getBoolean(PreferenceConstants.USE_PROXY, false); + + if (useProxy) + initializeTor(); + else + checkForTor(); + } /* * If Orbot/Tor is installed, prompt the user if they want to enable proxying for this session */ - public void checkForTor () + public boolean checkForTor () { OrbotHelper oh = new OrbotHelper(this); @@ -468,11 +473,16 @@ public class BrowserActivity extends Activity implements BrowserController { public void onClick(DialogInterface dialog, int which) { switch (which){ case DialogInterface.BUTTON_POSITIVE: + + mPreferences.edit().putBoolean(PreferenceConstants.USE_PROXY, true).apply(); + initializeTor (); + break; case DialogInterface.BUTTON_NEGATIVE: - //No button clicked + + mPreferences.edit().putBoolean(PreferenceConstants.USE_PROXY, false).apply(); break; } } @@ -481,7 +491,11 @@ public class BrowserActivity extends Activity implements BrowserController { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.use_tor_prompt).setPositiveButton(android.R.string.yes, dialogClickListener) .setNegativeButton(android.R.string.no, dialogClickListener).show(); + + return true; } + + return false; } /* @@ -496,7 +510,9 @@ public class BrowserActivity extends Activity implements BrowserController { WebkitProxy wkp = new WebkitProxy(); try { - wkp.setProxy("acr.browser.lightning.BrowserApp", getApplicationContext(), "localhost",8118); + String host = mPreferences.getString(PreferenceConstants.USE_PROXY_HOST, "localhost"); + int port = mPreferences.getInt(PreferenceConstants.USE_PROXY_PORT, 8118); + wkp.setProxy("acr.browser.lightning.BrowserApp", getApplicationContext(), host, port); } catch (Exception e) { Log.d("Lightning","error enabling web proxying",e); } @@ -1000,7 +1016,7 @@ public class BrowserActivity extends Activity implements BrowserController { private synchronized void newTab(String url, boolean show) { mIsNewIntent = false; - LightningView startingTab = new LightningView(mActivity, url); + LightningView startingTab = new LightningView(mActivity, url, mCookieManager); if (mIdGenerator == 0) { startingTab.resumeTimers(); } diff --git a/src/acr/browser/lightning/IncognitoActivity.java b/src/acr/browser/lightning/IncognitoActivity.java index bfdb20b..59aee64 100644 --- a/src/acr/browser/lightning/IncognitoActivity.java +++ b/src/acr/browser/lightning/IncognitoActivity.java @@ -908,7 +908,7 @@ public class IncognitoActivity extends Activity implements BrowserController { private synchronized void newTab(String url, boolean show) { mIsNewIntent = false; - LightningView startingTab = new LightningView(mActivity, url); + LightningView startingTab = new LightningView(mActivity, url, mCookieManager); if (mIdGenerator == 0) { startingTab.resumeTimers(); } diff --git a/src/acr/browser/lightning/LightningView.java b/src/acr/browser/lightning/LightningView.java index 5fceeb6..e3533a4 100644 --- a/src/acr/browser/lightning/LightningView.java +++ b/src/acr/browser/lightning/LightningView.java @@ -4,11 +4,19 @@ package acr.browser.lightning; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.http.util.ByteArrayBuffer; import android.annotation.SuppressLint; import android.app.Activity; @@ -28,10 +36,11 @@ import android.text.TextUtils; import android.text.method.PasswordTransformationMethod; import android.util.Log; import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.View; -import android.view.GestureDetector.SimpleOnGestureListener; import android.view.View.OnTouchListener; +import android.webkit.CookieManager; import android.webkit.GeolocationPermissions; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; @@ -44,7 +53,9 @@ import android.webkit.WebSettings.PluginState; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.LinearLayout; +import android.widget.VideoView; public class LightningView { @@ -62,9 +73,12 @@ public class LightningView { private static SharedPreferences mPreferences; private static boolean mWideViewPort; private static AdBlock mAdBlock; - - public LightningView(Activity activity, String url) { + private CookieManager mCookieManager; + + @SuppressLint("NewApi") + public LightningView(Activity activity, String url, CookieManager cookieManager) { mActivity = activity; + mCookieManager = cookieManager; mWebView = new WebView(activity); mTitle = new Title(activity); mAdBlock = new AdBlock(activity); @@ -249,6 +263,7 @@ public class LightningView { return Constants.FILE + homepage; } + @SuppressLint("NewApi") public synchronized void initializePreferences(Context context) { mPreferences = context.getSharedPreferences( PreferenceConstants.PREFERENCES, 0); @@ -348,7 +363,7 @@ public class LightningView { } } - @SuppressLint("SetJavaScriptEnabled") + @SuppressLint({ "SetJavaScriptEnabled", "NewApi" }) public void initializeSettings(WebSettings settings, Context context) { if (API < 18) { settings.setAppCacheMaxSize(Long.MAX_VALUE); @@ -357,7 +372,7 @@ public class LightningView { settings.setEnableSmoothTransition(true); } if (API > 16) { - settings.setMediaPlaybackRequiresUserGesture(true); + settings.setMediaPlaybackRequiresUserGesture(true); } if (API < 19) { settings.setDatabasePath(context.getFilesDir().getAbsolutePath() @@ -450,6 +465,7 @@ public class LightningView { } } + @SuppressLint("NewApi") public synchronized void find(String text) { if (mWebView != null) { if (API > 16) { @@ -536,6 +552,7 @@ public class LightningView { return ""; } + public class LightningWebClient extends WebViewClient { Context mActivity; @@ -548,14 +565,121 @@ public class LightningView { public WebResourceResponse shouldInterceptRequest(WebView view, String url) { if (mAdBlock.isAd(url)) { - Log.i("Blocked Domain:", url); ByteArrayInputStream EMPTY = new ByteArrayInputStream( "".getBytes()); WebResourceResponse response = new WebResourceResponse( "text/plain", "utf-8", EMPTY); return response; } - return super.shouldInterceptRequest(view, url); + + boolean useProxy = mPreferences.getBoolean(PreferenceConstants.USE_PROXY, false); + boolean mDoLeakHardening = false; + + if (!useProxy) + return null; + + if (!mDoLeakHardening) + return null; + + //now we are going to proxy! + try + { + + + URL uURl = new URL(url); + + Proxy proxy = null; + + String host = mPreferences.getString(PreferenceConstants.USE_PROXY_HOST, "localhost"); + int port = mPreferences.getInt(PreferenceConstants.USE_PROXY_PORT, 8118); + proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port)); + + HttpURLConnection.setFollowRedirects(true); + HttpURLConnection conn = (HttpURLConnection)uURl.openConnection(proxy); + conn.setInstanceFollowRedirects(true); + conn.setRequestProperty("User-Agent", mSettings.getUserAgentString()); + + //conn.setRequestProperty("Transfer-Encoding", "chunked"); + //conn.setUseCaches(false); + + final int bufferSize = 1024 * 32; + conn.setChunkedStreamingMode(bufferSize); + + String cType = conn.getContentType(); + String cEnc = conn.getContentEncoding(); + int connLen = conn.getContentLength(); + + + if (cType != null) + { + String[] ctArray = cType.split(";"); + cType = ctArray[0].trim(); + + if (cEnc == null && ctArray.length > 1) + { + cEnc = ctArray[1]; + if (cEnc.indexOf('=')!=-1) + cEnc = cEnc.split("=")[1].trim(); + } + } + + if (connLen <= 0) + connLen = 2048; + + if (cType != null && cType.startsWith("text")) + { + InputStream fStream = null; + + BufferedInputStream bis = new BufferedInputStream(conn.getInputStream()); + ByteArrayBuffer baf = new ByteArrayBuffer(connLen); + int read = 0; + int bufSize = 2048; + byte[] buffer = new byte[bufSize]; + while(true){ + read = bis.read(buffer); + if(read==-1){ + break; + } + baf.append(buffer, 0, read); + } + byte[] plainText = baf.toByteArray(); + + fStream = new ByteArrayInputStream(plainText); + + fStream = new ReplacingInputStream(new ByteArrayInputStream(plainText),"poster=".getBytes(),"foo=".getBytes()); + fStream = new ReplacingInputStream(fStream,"Poster=".getBytes(),"foo=".getBytes()); + fStream = new ReplacingInputStream(fStream,"Poster=".getBytes(),"foo=".getBytes()); + fStream = new ReplacingInputStream(fStream,".poster".getBytes(),".foo".getBytes()); + fStream = new ReplacingInputStream(fStream,"\"poster\"".getBytes(),"\"foo\"".getBytes()); + + WebResourceResponse response = new WebResourceResponse( + cType, cEnc, fStream); + + return response; + }/** + else if (mDoLeakHardening) + { + WebResourceResponse response = new WebResourceResponse( + cType, cEnc, conn.getInputStream()); + + return response; + + }*/ + else + { + return null; //let webkit handle it + } + } + catch (Exception e) + { + Log.e("Lightning","Error filtering stream",e); + ByteArrayInputStream EMPTY = new ByteArrayInputStream( + "".getBytes()); + WebResourceResponse response = new WebResourceResponse( + "text/plain", "utf-8", EMPTY); + return response; + } + } @Override @@ -871,9 +995,24 @@ public class LightningView { @Override public void onShowCustomView(View view, CustomViewCallback callback) { - Activity activity = mBrowserController.getActivity(); - mBrowserController.onShowCustomView(view, - activity.getRequestedOrientation(), callback); + + if (view instanceof FrameLayout){ + FrameLayout frame = (FrameLayout) view; + if (frame.getFocusedChild() instanceof VideoView){ + VideoView video = (VideoView) frame.getFocusedChild(); + video.stopPlayback(); + frame.removeView(video); + video.setVisibility(View.GONE); + } + } + else + { + Activity activity = mBrowserController.getActivity(); + mBrowserController.onShowCustomView(view, + activity.getRequestedOrientation(), callback); + + } + super.onShowCustomView(view, callback); } @@ -881,8 +1020,23 @@ public class LightningView { @Deprecated public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { - mBrowserController.onShowCustomView(view, requestedOrientation, - callback); + + if (view instanceof FrameLayout){ + FrameLayout frame = (FrameLayout) view; + if (frame.getFocusedChild() instanceof VideoView){ + VideoView video = (VideoView) frame.getFocusedChild(); + video.stopPlayback(); + frame.removeView(video); + video.setVisibility(View.GONE); + } + } + else + { + mBrowserController.onShowCustomView(view, requestedOrientation, + callback); + + } + super.onShowCustomView(view, requestedOrientation, callback); } diff --git a/src/acr/browser/lightning/PreferenceConstants.java b/src/acr/browser/lightning/PreferenceConstants.java index b3fd194..948a83d 100644 --- a/src/acr/browser/lightning/PreferenceConstants.java +++ b/src/acr/browser/lightning/PreferenceConstants.java @@ -31,4 +31,8 @@ public class PreferenceConstants { public static final String USER_AGENT = "agentchoose"; public static final String USER_AGENT_STRING = "userAgentString"; public static final String GOOGLE_SEARCH_SUGGESTIONS = "GoogleSearchSuggestions"; + + public static final String USE_PROXY = "useProxy"; + public static final String USE_PROXY_HOST = "useProxyHost"; + public static final String USE_PROXY_PORT = "useProxyPort"; } diff --git a/src/acr/browser/lightning/ReplacingInputStream.java b/src/acr/browser/lightning/ReplacingInputStream.java new file mode 100644 index 0000000..9d9db1c --- /dev/null +++ b/src/acr/browser/lightning/ReplacingInputStream.java @@ -0,0 +1,91 @@ +package acr.browser.lightning; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Iterator; +import java.util.LinkedList; + +public class ReplacingInputStream extends FilterInputStream { + + LinkedList inQueue = new LinkedList(); + LinkedList outQueue = new LinkedList(); + final byte[] search, replacement; + + protected ReplacingInputStream(InputStream in, byte[] search, + byte[] replacement) { + super(in); + this.search = search; + this.replacement = replacement; + } + + private boolean isMatchFound() { + Iterator inIter = inQueue.iterator(); + for (int i = 0; i < search.length; i++) + if (!inIter.hasNext() || search[i] != inIter.next()) + return false; + return true; + } + + private void readAhead() throws IOException { + // Work up some look-ahead. + while (inQueue.size() < search.length) { + int next = super.read(); + inQueue.offer(next); + if (next == -1) + break; + } + } + + + @Override + public int read() throws IOException { + + // Next byte already determined. + if (outQueue.isEmpty()) { + + readAhead(); + + if (isMatchFound()) { + for (int i = 0; i < search.length; i++) + inQueue.remove(); + + for (byte b : replacement) + outQueue.offer((int) b); + } else + outQueue.add(inQueue.remove()); + } + + return outQueue.remove(); + } + + /** + * Returns false. REFilterInputStream does not support mark() and + * reset() methods. + */ + public boolean markSupported() { + return false; + } + + /** Reads from the stream into the provided array. + * @throws IOException */ + public int read(byte[] b, int off, int len) throws IOException { + int i; + int ok = 0; + while (len-- > 0) { + i = read(); + if (i == -1) return (ok == 0) ? -1 : ok; + b[off++] = (byte) i; + ok++; + } + return ok; + } + + @Override + public int read(byte[] buffer) throws IOException { + + return read(buffer, 0, buffer.length); + } + + +} \ No newline at end of file