Improve proxy

This commit is contained in:
tom79 2019-05-24 15:13:28 +02:00
parent 67ab25a60a
commit bb158d821d
12 changed files with 292 additions and 37 deletions

View File

@ -16,6 +16,7 @@ package app.fedilab.android.activities;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
@ -149,7 +150,7 @@ public class LoginActivity extends BaseActivity {
@Override
public void run() {
try {
final String response = new HttpsConnection(LoginActivity.this, instance).post(Helper.instanceWithProtocol(instance) + action, 30, parameters, null);
final String response = new HttpsConnection(LoginActivity.this, instance).post(Helper.instanceWithProtocol(LoginActivity.this, instance) + action, 30, parameters, null);
JSONObject resobj;
try {
resobj = new JSONObject(response);
@ -542,9 +543,9 @@ public class LoginActivity extends BaseActivity {
try {
String response;
if( socialNetwork == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE)
response = new HttpsConnection(LoginActivity.this, instance).get(Helper.instanceWithProtocol(instance) + actionToken, 30, parameters, null );
response = new HttpsConnection(LoginActivity.this, instance).get(Helper.instanceWithProtocol(getApplicationContext(),instance) + actionToken, 30, parameters, null );
else
response = new HttpsConnection(LoginActivity.this, instance).post(Helper.instanceWithProtocol(instance) + actionToken, 30, parameters, null );
response = new HttpsConnection(LoginActivity.this, instance).post(Helper.instanceWithProtocol(getApplicationContext(), instance) + actionToken, 30, parameters, null );
runOnUiThread(new Runnable() {
public void run() {
JSONObject resobj;
@ -647,9 +648,9 @@ public class LoginActivity extends BaseActivity {
try {
String response;
if( socialNetwork != UpdateAccountInfoAsyncTask.SOCIAL.GNU)
response = new HttpsConnection(LoginActivity.this, instance).post(Helper.instanceWithProtocol(instance) + finalOauthUrl, 30, parameters, null );
response = new HttpsConnection(LoginActivity.this, instance).post(Helper.instanceWithProtocol(getApplicationContext(),instance) + finalOauthUrl, 30, parameters, null );
else {
response = new HttpsConnection(LoginActivity.this, instance).get(Helper.instanceWithProtocol(instance) + finalOauthUrl, 30, null, basicAuth);
response = new HttpsConnection(LoginActivity.this, instance).get(Helper.instanceWithProtocol(getApplicationContext(),instance) + finalOauthUrl, 30, null, basicAuth);
}
runOnUiThread(new Runnable() {
public void run() {
@ -751,7 +752,7 @@ public class LoginActivity extends BaseActivity {
i.putExtra("instance", instance);
startActivity(i);
}else{
String url = redirectUserToAuthorizeAndLogin(socialNetwork, client_id, instance);
String url = redirectUserToAuthorizeAndLogin(getApplicationContext(), socialNetwork, client_id, instance);
Helper.openBrowser(LoginActivity.this, url);
@ -848,13 +849,13 @@ public class LoginActivity extends BaseActivity {
}
public static String redirectUserToAuthorizeAndLogin(UpdateAccountInfoAsyncTask.SOCIAL socialNetwork, String clientId, String instance) {
public static String redirectUserToAuthorizeAndLogin(Context context, UpdateAccountInfoAsyncTask.SOCIAL socialNetwork, String clientId, String instance) {
String queryString = Helper.CLIENT_ID + "="+ clientId;
queryString += "&" + Helper.REDIRECT_URI + "="+ Uri.encode(Helper.REDIRECT_CONTENT_WEB);
queryString += "&" + Helper.RESPONSE_TYPE +"=code";
if( socialNetwork != UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED )
queryString += "&" + Helper.SCOPE +"=" + Helper.OAUTH_SCOPES;
return Helper.instanceWithProtocol(instance) + Helper.EP_AUTHORIZE + "?" + queryString;
return Helper.instanceWithProtocol(context, instance) + Helper.EP_AUTHORIZE + "?" + queryString;
}

View File

@ -55,6 +55,7 @@ import app.fedilab.android.sqlite.DomainBlockDAO;
import app.fedilab.android.sqlite.Sqlite;
import app.fedilab.android.webview.MastalabWebChromeClient;
import app.fedilab.android.webview.MastalabWebViewClient;
import app.fedilab.android.webview.ProxyHelper;
import es.dmoral.toasty.Toasty;
import app.fedilab.android.R;
@ -111,6 +112,15 @@ public class WebviewActivity extends BaseActivity {
FrameLayout webview_container = findViewById(R.id.webview_container);
final ViewGroup videoLayout = findViewById(R.id.videoLayout); // Your own view, read class comments
webView.getSettings().setJavaScriptEnabled(true);
boolean proxyEnabled = sharedpreferences.getBoolean(Helper.SET_PROXY_ENABLED, false);
if( proxyEnabled ){
String host = sharedpreferences.getString(Helper.SET_PROXY_HOST, "127.0.0.1");
int port = sharedpreferences.getInt(Helper.SET_PROXY_PORT, 8118);
ProxyHelper.setProxy(getApplicationContext(), webView,host, port,WebviewActivity.class.getName());
}
MastalabWebChromeClient mastalabWebChromeClient = new MastalabWebChromeClient(WebviewActivity.this, webView, webview_container, videoLayout);
mastalabWebChromeClient.setOnToggledFullscreen(new MastalabWebChromeClient.ToggledFullscreenCallback() {
@Override

View File

@ -42,10 +42,13 @@ import android.widget.Toast;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashMap;
import app.fedilab.android.client.HttpsConnection;
import app.fedilab.android.helper.Helper;
import app.fedilab.android.webview.ProxyHelper;
import es.dmoral.toasty.Toasty;
import app.fedilab.android.R;
import app.fedilab.android.asynctasks.UpdateAccountInfoAsyncTask;
@ -129,6 +132,12 @@ public class WebviewConnectActivity extends BaseActivity {
}
});
boolean proxyEnabled = sharedpreferences.getBoolean(Helper.SET_PROXY_ENABLED, false);
if( proxyEnabled ){
String host = sharedpreferences.getString(Helper.SET_PROXY_HOST, "127.0.0.1");
int port = sharedpreferences.getInt(Helper.SET_PROXY_PORT, 8118);
ProxyHelper.setProxy(getApplicationContext(), webView,host, port,WebviewConnectActivity.class.getName());
}
webView.setWebViewClient(new WebViewClient() {
@SuppressWarnings("deprecation")
@ -156,7 +165,7 @@ public class WebviewConnectActivity extends BaseActivity {
@Override
public void run() {
try {
final String response = new HttpsConnection(WebviewConnectActivity.this, instance).post(Helper.instanceWithProtocol(instance) + action, 30, parameters, null);
final String response = new HttpsConnection(WebviewConnectActivity.this, instance).post(Helper.instanceWithProtocol(getApplicationContext(), instance) + action, 30, parameters, null);
JSONObject resobj;
try {
resobj = new JSONObject(response);
@ -179,7 +188,7 @@ public class WebviewConnectActivity extends BaseActivity {
}
});
webView.loadUrl(LoginActivity.redirectUserToAuthorizeAndLogin(social, clientId, instance));
webView.loadUrl(LoginActivity.redirectUserToAuthorizeAndLogin(getApplicationContext(), social, clientId, instance));
}

View File

@ -41,7 +41,7 @@ public class RetrieveRemoteDataAsyncTask extends AsyncTask<Void, Void, Void> {
public RetrieveRemoteDataAsyncTask(Context context, String username, String instance, OnRetrieveRemoteAccountInterface onRetrieveRemoteAccountInterface){
this.url = Helper.instanceWithProtocol(instance) + "/@" + username;
this.url = Helper.instanceWithProtocol(context, instance) + "/@" + username;
this.listener = onRetrieveRemoteAccountInterface;
this.contextReference = new WeakReference<>(context);
}

View File

@ -20,6 +20,7 @@ import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import com.google.gson.JsonArray;
@ -179,10 +180,35 @@ public class API {
}
public InstanceNodeInfo getNodeInfo(String domain){
//Try to guess URL scheme for the onion instance
String scheme = "https";
if( domain.endsWith(".onion")){
try {
new HttpsConnection(context, domain).get("http://" + domain, 30, null, null);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.SET_ONION_SCHEME + domain, "http");
scheme = "http";
editor.apply();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (HttpsConnection.HttpsConnectionException e) {
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.SET_ONION_SCHEME + domain, "https");
editor.apply();
}
}
Log.v(Helper.TAG,"scheme: " + scheme);
String response;
InstanceNodeInfo instanceNodeInfo = new InstanceNodeInfo();
try {
response = new HttpsConnection(context, domain).get("https://" + domain + "/.well-known/nodeinfo", 30, null, null);
response = new HttpsConnection(context, domain).get(scheme+"://" + domain + "/.well-known/nodeinfo", 30, null, null);
JSONArray jsonArray = new JSONObject(response).getJSONArray("links");
ArrayList<NodeInfo> nodeInfos = new ArrayList<>();
try {
@ -229,7 +255,7 @@ public class API {
e.printStackTrace();
} catch (HttpsConnection.HttpsConnectionException e) {
try {
response = new HttpsConnection(context, this.instance).get("https://" + domain + "/api/v1/instance", 30, null, null);
response = new HttpsConnection(context, this.instance).get(scheme+"://" + domain + "/api/v1/instance", 30, null, null);
JSONObject jsonObject = new JSONObject(response);
instanceNodeInfo.setName("MASTODON");
instanceNodeInfo.setVersion(jsonObject.getString("version"));
@ -4961,10 +4987,10 @@ public class API {
private String getAbsoluteUrl(String action) {
return Helper.instanceWithProtocol(this.instance) + "/api/v1" + action;
return Helper.instanceWithProtocol(this.context, this.instance) + "/api/v1" + action;
}
private String getAbsoluteUr2l(String action) {
return Helper.instanceWithProtocol(this.instance) + "/api/v2" + action;
return Helper.instanceWithProtocol(this.context, this.instance) + "/api/v2" + action;
}
private String getAbsoluteUrlRemote(String remote, String action) {
return "https://" + remote + "/api/v1" + action;

View File

@ -2357,7 +2357,7 @@ public class GNUAPI {
private String getAbsoluteUrl(String action) {
return Helper.instanceWithProtocol(this.instance) + "/api" + action;
return Helper.instanceWithProtocol(this.context, this.instance) + "/api" + action;
}

View File

@ -14,6 +14,7 @@ package app.fedilab.android.client;
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
@ -54,7 +55,9 @@ import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
@ -122,6 +125,16 @@ public class HttpsConnection {
}
}
if( instance != null && instance.endsWith(".onion")) {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@SuppressLint("BadHostnameVerifier")
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
}
}

View File

@ -2265,7 +2265,7 @@ public class PeertubeAPI {
private String getAbsoluteUrl(String action) {
return Helper.instanceWithProtocol(this.instance) + "/api/v1" + action;
return Helper.instanceWithProtocol(this.context, this.instance) + "/api/v1" + action;
}
private String getAbsoluteUrlRemote(String remote, String action) {
return "https://" + remote + "/api/v1" + action;

View File

@ -36,28 +36,31 @@ public class TLSSocketFactory extends SSLSocketFactory {
public TLSSocketFactory(String instance) throws KeyManagementException, NoSuchAlgorithmException {
sslContext = SSLContext.getInstance("TLS");
if( instance == null || !instance.endsWith(".onion")) {
sslContext = SSLContext.getInstance("TLS");
isOnion = false;
sslContext.init(null, null, null);
}else{
sslContext = SSLContext.getInstance("SSL");
isOnion = true;
TrustManager tm = new X509TrustManager() {
@SuppressLint("TrustAllX509TrustManager")
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@SuppressLint("TrustAllX509TrustManager")
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
sslContext.init(null, new TrustManager[]{tm}, null);
sslContext.init(null, trustAllCerts, null);
}
Log.v(Helper.TAG,instance + " -> " + isOnion);
sSLSocketFactory = sslContext.getSocketFactory();
}

View File

@ -327,7 +327,7 @@ public class Helper {
public static final String SET_BLUR_SENSITIVE = "set_blur_sensitive";
public static final String SET_LONG_PRESS_MEDIA = "set_long_press_media";
public static final String SET_DISPLAY_TIMELINE_IN_LIST = "set_display_timeline_in_list";
public static final String SET_ONION_SCHEME = "set_onion_scheme";
public static final int S_NO = 0;
static final int S_512KO = 1;
public static final int S_1MO = 2;
@ -1232,16 +1232,19 @@ public class Helper {
}
public static String getLiveInstanceWithProtocol(Context context) {
return instanceWithProtocol(getLiveInstance(context));
return instanceWithProtocol(context, getLiveInstance(context));
}
public static String instanceWithProtocol(String instance){
public static String instanceWithProtocol(Context context, String instance){
if( instance == null)
return null;
if( instance.endsWith(".onion"))
return "http://" + instance;
else
if( instance.endsWith(".onion")) {
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String scheme = sharedpreferences.getString(SET_ONION_SCHEME+instance, "http");
return scheme + "://" + instance;
}else {
return "https://" + instance;
}
}

View File

@ -17,10 +17,12 @@ package app.fedilab.android.webview;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@ -34,6 +36,7 @@ import java.util.List;
import app.fedilab.android.R;
import app.fedilab.android.activities.WebviewActivity;
import app.fedilab.android.helper.Helper;
/**
* Created by Thomas on 25/06/2017.
@ -104,6 +107,16 @@ public class MastalabWebViewClient extends WebViewClient {
return this.domains;
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
String instance = Helper.getLiveInstance(activity);
if( instance != null && instance.endsWith(".onion")) {
handler.proceed();
}else{
super.onReceivedSslError(view, handler, error);
}
}
@Override
public void onPageStarted (WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);

View File

@ -0,0 +1,177 @@
package app.fedilab.android.webview;
/* Copyright 2019 Thomas Schneider
*
* This file is a part of Fedilab
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Fedilab; if not,
* see <http://www.gnu.org/licenses>. */
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.net.Proxy;
import android.os.Build;
import android.os.Parcelable;
import android.util.ArrayMap;
import android.webkit.WebView;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ProxyHelper {
public static void setProxy(Context context, WebView webview, String host, int port, String applicationClassName) {
if (Build.VERSION.SDK_INT <= 18) {
setProxyJB(webview, host, port);
}
// 4.4 (KK) & 5.0 (Lollipop)
else {
setProxyKKPlus(context, webview, host, port, applicationClassName);
}
}
@SuppressWarnings("all")
private static boolean setProxyICS(WebView webview, String host, int port) {
try
{
Class jwcjb = Class.forName("android.webkit.JWebCoreJavaBridge");
Class params[] = new Class[1];
params[0] = Class.forName("android.net.ProxyProperties");
Method updateProxyInstance = jwcjb.getDeclaredMethod("updateProxy", params);
Class wv = Class.forName("android.webkit.WebView");
Field mWebViewCoreField = wv.getDeclaredField("mWebViewCore");
Object mWebViewCoreFieldInstance = getFieldValueSafely(mWebViewCoreField, webview);
Class wvc = Class.forName("android.webkit.WebViewCore");
Field mBrowserFrameField = wvc.getDeclaredField("mBrowserFrame");
Object mBrowserFrame = getFieldValueSafely(mBrowserFrameField, mWebViewCoreFieldInstance);
Class bf = Class.forName("android.webkit.BrowserFrame");
Field sJavaBridgeField = bf.getDeclaredField("sJavaBridge");
Object sJavaBridge = getFieldValueSafely(sJavaBridgeField, mBrowserFrame);
Class ppclass = Class.forName("android.net.ProxyProperties");
Class pparams[] = new Class[3];
pparams[0] = String.class;
pparams[1] = int.class;
pparams[2] = String.class;
Constructor ppcont = ppclass.getConstructor(pparams);
updateProxyInstance.invoke(sJavaBridge, ppcont.newInstance(host, port, null));
return true;
}
catch (Exception ex) {
return false;
}
}
/**
* Set Proxy for Android 4.1 - 4.3.
*/
@SuppressWarnings("all")
private static boolean setProxyJB(WebView webview, String host, int port) {
try {
Class wvcClass = Class.forName("android.webkit.WebViewClassic");
Class wvParams[] = new Class[1];
wvParams[0] = Class.forName("android.webkit.WebView");
Method fromWebView = wvcClass.getDeclaredMethod("fromWebView", wvParams);
Object webViewClassic = fromWebView.invoke(null, webview);
Class wv = Class.forName("android.webkit.WebViewClassic");
Field mWebViewCoreField = wv.getDeclaredField("mWebViewCore");
Object mWebViewCoreFieldInstance = getFieldValueSafely(mWebViewCoreField, webViewClassic);
Class wvc = Class.forName("android.webkit.WebViewCore");
Field mBrowserFrameField = wvc.getDeclaredField("mBrowserFrame");
Object mBrowserFrame = getFieldValueSafely(mBrowserFrameField, mWebViewCoreFieldInstance);
Class bf = Class.forName("android.webkit.BrowserFrame");
Field sJavaBridgeField = bf.getDeclaredField("sJavaBridge");
Object sJavaBridge = getFieldValueSafely(sJavaBridgeField, mBrowserFrame);
Class ppclass = Class.forName("android.net.ProxyProperties");
Class pparams[] = new Class[3];
pparams[0] = String.class;
pparams[1] = int.class;
pparams[2] = String.class;
Constructor ppcont = ppclass.getConstructor(pparams);
Class jwcjb = Class.forName("android.webkit.JWebCoreJavaBridge");
Class params[] = new Class[1];
params[0] = Class.forName("android.net.ProxyProperties");
Method updateProxyInstance = jwcjb.getDeclaredMethod("updateProxy", params);
updateProxyInstance.invoke(sJavaBridge, ppcont.newInstance(host, port, null));
} catch (Exception ex) {
return false;
}
return true;
}
// from https://stackoverflow.com/questions/19979578/android-webview-set-proxy-programatically-kitkat
@SuppressLint("NewApi")
@SuppressWarnings("all")
private static void setProxyKKPlus(Context appContext, WebView webView, String host, int port, String applicationClassName) {
System.setProperty("http.proxyHost", host);
System.setProperty("http.proxyPort", port + "");
System.setProperty("https.proxyHost", host);
System.setProperty("https.proxyPort", port + "");
try {
Class applictionCls = Class.forName("android.app.Application");
Field loadedApkField = applictionCls.getDeclaredField("mLoadedApk");
loadedApkField.setAccessible(true);
Object loadedApk = loadedApkField.get(appContext);
Class loadedApkCls = Class.forName("android.app.LoadedApk");
Field receiversField = loadedApkCls.getDeclaredField("mReceivers");
receiversField.setAccessible(true);
ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk);
for (Object receiverMap : receivers.values()) {
for (Object rec : ((ArrayMap) receiverMap).keySet()) {
Class clazz = rec.getClass();
if (clazz.getName().contains("ProxyChangeListener")) {
Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, Intent.class);
Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
onReceiveMethod.invoke(rec, appContext, intent);
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private static Object getFieldValueSafely(Field field, Object classInstance) throws IllegalArgumentException, IllegalAccessException {
boolean oldAccessibleValue = field.isAccessible();
field.setAccessible(true);
Object result = field.get(classInstance);
field.setAccessible(oldAccessibleValue);
return result;
}
}