diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..eb47d8e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "external/netcipher"] + path = external/netcipher + url = https://github.com/guardianproject/NetCipher.git diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 3038765..d7835ac 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -31,6 +31,7 @@ android:targetSdkVersion="19" /> ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/layout/settings.xml b/res/layout/settings.xml index 824df1a..4dda43a 100644 --- a/res/layout/settings.xml +++ b/res/layout/settings.xml @@ -69,7 +69,7 @@ - Powered by Google AdBlock AdBlock is currently only available in the browser\'s paid version, Lightning Browser+. It is available to download on Google Play. + It looks like you have Orbot installed. Do you want to use Tor? \ No newline at end of file diff --git a/setup-ant.sh b/setup-ant.sh new file mode 100755 index 0000000..d5d6414 --- /dev/null +++ b/setup-ant.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +target="android-19" + +for f in `find external/ -name project.properties`; do +projectdir=`dirname $f` + echo "Updating ant setup in $projectdir:" + android update lib-project -p $projectdir -t $target +done +android update project -p . --subprojects -t $target --name Lightning + +cp libs/android-support-v4.jar external/netcipher/libnetcipher/libs/android-support-v4.jar + diff --git a/src/acr/browser/lightning/BrowserActivity.java b/src/acr/browser/lightning/BrowserActivity.java index 35edae4..06865d8 100644 --- a/src/acr/browser/lightning/BrowserActivity.java +++ b/src/acr/browser/lightning/BrowserActivity.java @@ -4,6 +4,9 @@ package acr.browser.lightning; +import info.guardianproject.onionkit.ui.OrbotHelper; +import info.guardianproject.onionkit.web.WebkitProxy; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -73,27 +76,27 @@ import android.view.Window; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.webkit.ValueCallback; -import android.webkit.WebChromeClient.CustomViewCallback; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient.CustomViewCallback; import android.webkit.WebIconDatabase; import android.webkit.WebView; import android.webkit.WebView.HitTestResult; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.AutoCompleteTextView; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; -import android.widget.VideoView; import android.widget.TextView.OnEditorActionListener; +import android.widget.VideoView; public class BrowserActivity extends Activity implements BrowserController { private static DrawerLayout mDrawerLayout; @@ -445,8 +448,76 @@ public class BrowserActivity extends Activity implements BrowserController { WebIconDatabase.getInstance().open( getDir("icons", MODE_PRIVATE).getPath()); } + + 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 boolean checkForTor () + { + + OrbotHelper oh = new OrbotHelper(this); + if (oh.isOrbotInstalled()) + { + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + 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: + + mPreferences.edit().putBoolean(PreferenceConstants.USE_PROXY, false).apply(); + break; + } + } + }; + + 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; } - + + /* + * Initialize WebKit Proxying for Tor + */ + public void initializeTor () + { + + OrbotHelper oh = new OrbotHelper(this); + if (!oh.isOrbotRunning()) + oh.requestOrbotStart(this); + + WebkitProxy wkp = new WebkitProxy(); + try { + 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); + } + } + public synchronized void initializeTabs() { mIdGenerator = 0; @@ -945,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/BrowserApp.java b/src/acr/browser/lightning/BrowserApp.java new file mode 100644 index 0000000..3b01e4f --- /dev/null +++ b/src/acr/browser/lightning/BrowserApp.java @@ -0,0 +1,7 @@ +package acr.browser.lightning; + +import android.app.Application; + +public class BrowserApp extends Application { + +} 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