Browse Source

Merge pull request #82 from n8fr8/master

Initial work to support Tor (Orbot) and proxying
master
Anthony Restaino 10 years ago
parent
commit
94d39dd56b
  1. 3
      .gitmodules
  2. 1
      AndroidManifest.xml
  3. 1
      external/netcipher
  4. 61
      proguard-project.txt
  5. 2
      res/layout/settings.xml
  6. 1
      res/values/strings.xml
  7. 13
      setup-ant.sh
  8. 83
      src/acr/browser/lightning/BrowserActivity.java
  9. 7
      src/acr/browser/lightning/BrowserApp.java
  10. 2
      src/acr/browser/lightning/IncognitoActivity.java
  11. 178
      src/acr/browser/lightning/LightningView.java
  12. 4
      src/acr/browser/lightning/PreferenceConstants.java
  13. 91
      src/acr/browser/lightning/ReplacingInputStream.java

3
.gitmodules vendored

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
[submodule "external/netcipher"]
path = external/netcipher
url = https://github.com/guardianproject/NetCipher.git

1
AndroidManifest.xml

@ -31,6 +31,7 @@ @@ -31,6 +31,7 @@
android:targetSdkVersion="19" />
<application
android:name="acr.browser.lightning.BrowserApp"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"

1
external/netcipher vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
Subproject commit ba9d13676669f7ef32df43a023a4d4a33385e013

61
proguard-project.txt

@ -0,0 +1,61 @@ @@ -0,0 +1,61 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(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 *;
}

2
res/layout/settings.xml

@ -69,7 +69,7 @@ @@ -69,7 +69,7 @@
</RelativeLayout>
</RelativeLayout>
<LinearLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="5dp"

1
res/values/strings.xml

@ -158,5 +158,6 @@ @@ -158,5 +158,6 @@
<string name="powered_by_google">Powered by Google</string>
<string name="title_adblock">AdBlock</string>
<string name="message_adblock">AdBlock is currently only available in the browser\'s paid version, Lightning Browser+. It is available to download on Google Play.</string>
<string name="use_tor_prompt">It looks like you have Orbot installed. Do you want to use Tor?</string>
</resources>

13
setup-ant.sh

@ -0,0 +1,13 @@ @@ -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

83
src/acr/browser/lightning/BrowserActivity.java

@ -4,6 +4,9 @@ @@ -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; @@ -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 { @@ -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 { @@ -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();
}

7
src/acr/browser/lightning/BrowserApp.java

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
package acr.browser.lightning;
import android.app.Application;
public class BrowserApp extends Application {
}

2
src/acr/browser/lightning/IncognitoActivity.java

@ -908,7 +908,7 @@ public class IncognitoActivity extends Activity implements BrowserController { @@ -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();
}

178
src/acr/browser/lightning/LightningView.java

@ -4,11 +4,19 @@ @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -536,6 +552,7 @@ public class LightningView {
return "";
}
public class LightningWebClient extends WebViewClient {
Context mActivity;
@ -548,14 +565,121 @@ public class LightningView { @@ -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 { @@ -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 { @@ -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);
}

4
src/acr/browser/lightning/PreferenceConstants.java

@ -31,4 +31,8 @@ public class PreferenceConstants { @@ -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";
}

91
src/acr/browser/lightning/ReplacingInputStream.java

@ -0,0 +1,91 @@ @@ -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<Integer> inQueue = new LinkedList<Integer>();
LinkedList<Integer> outQueue = new LinkedList<Integer>();
final byte[] search, replacement;
protected ReplacingInputStream(InputStream in, byte[] search,
byte[] replacement) {
super(in);
this.search = search;
this.replacement = replacement;
}
private boolean isMatchFound() {
Iterator<Integer> 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);
}
}
Loading…
Cancel
Save