From 4d51aa9acb8daaec1326f14e5025fde6d1f0dcd8 Mon Sep 17 00:00:00 2001 From: Soren Stoutner Date: Sat, 15 Feb 2020 00:32:21 -0700 Subject: [PATCH] Respect proxies when getting source and saving URLs. https://redmine.stoutner.com/issues/524 --- .../activities/MainWebViewActivity.java | 19 +++-- .../activities/ViewSourceActivity.java | 8 +- .../privacybrowser/asynctasks/GetSource.java | 30 +++++-- .../privacybrowser/asynctasks/SaveUrl.java | 22 +++-- .../privacybrowser/helpers/ProxyHelper.java | 84 +++++++++++++++++-- 5 files changed, 131 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java index 166dc35b..5e4d3eeb 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java @@ -202,10 +202,15 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook public final static int DOMAINS_CUSTOM_USER_AGENT = 13; // Start activity for result request codes. The public static entries are accessed from `OpenDialog()` and `SaveWebpageDialog()`. - public static final int BROWSE_OPEN_REQUEST_CODE = 0; - public static final int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1; + public final static int BROWSE_OPEN_REQUEST_CODE = 0; + public final static int BROWSE_SAVE_WEBPAGE_REQUEST_CODE = 1; private final int BROWSE_FILE_UPLOAD_REQUEST_CODE = 2; + // The proxy mode is public static so it can be accessed from `ProxyHelper()`. + // It is also used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`. + // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes. + public static String proxyMode = ProxyHelper.NONE; + // The permission result request codes are used in `onCreateContextMenu()`, `onCloseDownloadLocationPermissionDialog()`, `onRequestPermissionResult()`, `onSaveWebpage()`, // `onCloseStoragePermissionDialog()`, and `initializeWebView()`. @@ -240,10 +245,6 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // `webViewDefaultUserAgent` is used in `onCreate()` and `onPrepareOptionsMenu()`. private String webViewDefaultUserAgent; - // The proxy mode is used in `onRestart()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyAppSettings()`, and `applyProxy()`. - // It will be updated in `applyAppSettings()`, but it needs to be initialized here or the first run of `onPrepareOptionsMenu()` crashes. - private String proxyMode = ProxyHelper.NONE; - // The incognito mode is set in `applyAppSettings()` and used in `initializeWebView()`. private boolean incognitoModeEnabled; @@ -3166,7 +3167,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook switch (saveType) { case StoragePermissionDialog.SAVE: // Save the URL. - new SaveUrl(this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); + new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); break; case StoragePermissionDialog.SAVE_AS_ARCHIVE: @@ -3195,7 +3196,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook switch (saveType) { case StoragePermissionDialog.SAVE: // Save the URL. - new SaveUrl(this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); + new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); break; case StoragePermissionDialog.SAVE_AS_ARCHIVE: @@ -3346,7 +3347,7 @@ public class MainWebViewActivity extends AppCompatActivity implements CreateBook // Check to see if the storage permission was granted. If the dialog was canceled the grant results will be empty. if ((grantResults.length > 0) && (grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // The storage permission was granted. // Save the raw URL. - new SaveUrl(this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); + new SaveUrl(this, this, saveWebpageFilePath, currentWebView.getSettings().getUserAgentString(), currentWebView.getAcceptFirstPartyCookies()).execute(saveWebpageUrl); } else { // The storage permission was not granted. // Display an error snackbar. Snackbar.make(currentWebView, getString(R.string.cannot_use_location), Snackbar.LENGTH_LONG).show(); diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java index 229bcf28..a0f51d14 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java +++ b/app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java @@ -1,5 +1,5 @@ /* - * Copyright © 2017-2019 Soren Stoutner . + * Copyright © 2017-2020 Soren Stoutner . * * This file is part of Privacy Browser . * @@ -161,7 +161,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get new source data for the current URL if it beings with `http`. if (url.startsWith("http")) { - new GetSource(this, userAgent).execute(url); + new GetSource(this, this, userAgent).execute(url); } // Consume the key press. @@ -182,7 +182,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get new source data for the URL if it begins with `http`. if (url.startsWith("http")) { - new GetSource(this, userAgent).execute(url); + new GetSource(this, this, userAgent).execute(url); } else { // Stop the refresh animation. swipeRefreshLayout.setRefreshing(false); @@ -199,7 +199,7 @@ public class ViewSourceActivity extends AppCompatActivity { // Get the source using an AsyncTask if the URL begins with `http`. if ((currentUrl != null) && currentUrl.startsWith("http")) { - new GetSource(this, userAgent).execute(currentUrl); + new GetSource(this, this, userAgent).execute(currentUrl); } } diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java index c9a9ea33..415f35bd 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java @@ -20,6 +20,7 @@ package com.stoutner.privacybrowser.asynctasks; import android.app.Activity; +import android.content.Context; import android.content.SharedPreferences; import android.graphics.Typeface; import android.os.AsyncTask; @@ -37,6 +38,7 @@ import android.widget.TextView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.helpers.ProxyHelper; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; @@ -44,19 +46,22 @@ import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; import java.util.Locale; // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder[]` contains the results. public class GetSource extends AsyncTask { - // Declare a weak reference to the calling activity. + // Define weak references to the calling context and activity. + private WeakReference contextWeakReference; private WeakReference activityWeakReference; // Store the user agent. private String userAgent; - public GetSource(Activity activity, String userAgent) { - // Populate the weak reference to the calling activity. + public GetSource(Context context, Activity activity, String userAgent) { + // Populate the weak references to the calling context and activity. + contextWeakReference = new WeakReference<>(context); activityWeakReference = new WeakReference<>(activity); // Store the user agent. @@ -66,16 +71,16 @@ public class GetSource extends AsyncTask // `onPreExecute()` operates on the UI thread. @Override protected void onPreExecute() { - // Get a handle for the activity. - Activity viewSourceActivity = activityWeakReference.get(); + // Get a handle for the calling activity. + Activity activity = activityWeakReference.get(); // Abort if the activity is gone. - if ((viewSourceActivity == null) || viewSourceActivity.isFinishing()) { + if ((activity == null) || activity.isFinishing()) { return; } // Get a handle for the progress bar. - ProgressBar progressBar = viewSourceActivity.findViewById(R.id.progress_bar); + ProgressBar progressBar = activity.findViewById(R.id.progress_bar); // Make the progress bar visible. progressBar.setVisibility(View.VISIBLE); @@ -92,7 +97,8 @@ public class GetSource extends AsyncTask SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder(); SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder(); - // Get a handle for the activity. + // Get a handle for the context and activity. + Context context = contextWeakReference.get(); Activity activity = activityWeakReference.get(); // Abort if the activity is gone. @@ -105,8 +111,14 @@ public class GetSource extends AsyncTask // Get the current URL from the main activity. URL url = new URL(formattedUrlString[0]); + // Instantiate the proxy helper. + ProxyHelper proxyHelper = new ProxyHelper(); + + // Get the current proxy. + Proxy proxy = proxyHelper.getCurrentProxy(context); + // Open a connection to the URL. No data is actually sent at this point. - HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(); + HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy); // Define the variables necessary to build the request headers. requestHeadersBuilder = new SpannableStringBuilder(); diff --git a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java index fd72db47..1eb50323 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java +++ b/app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java @@ -20,11 +20,13 @@ package com.stoutner.privacybrowser.asynctasks; import android.app.Activity; +import android.content.Context; import android.os.AsyncTask; import android.webkit.CookieManager; import com.google.android.material.snackbar.Snackbar; import com.stoutner.privacybrowser.R; +import com.stoutner.privacybrowser.helpers.ProxyHelper; import com.stoutner.privacybrowser.views.NoSwipeViewPager; import java.io.BufferedInputStream; @@ -35,10 +37,12 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.net.HttpURLConnection; +import java.net.Proxy; import java.net.URL; public class SaveUrl extends AsyncTask { - // Define a weak reference to the calling activity. + // Define a weak references for the calling context and activities. + private WeakReference contextWeakReference; private WeakReference activityWeakReference; // Define a success string constant. @@ -51,8 +55,9 @@ public class SaveUrl extends AsyncTask { private Snackbar savingFileSnackbar; // The public constructor. - public SaveUrl(Activity activity, String filePathString, String userAgent, boolean cookiesEnabled) { - // Populate the weak reference to the calling activity. + public SaveUrl(Context context, Activity activity, String filePathString, String userAgent, boolean cookiesEnabled) { + // Populate weak references to the calling context and activity. + contextWeakReference = new WeakReference<>(context); activityWeakReference = new WeakReference<>(activity); // Store the class variables. @@ -84,7 +89,8 @@ public class SaveUrl extends AsyncTask { @Override protected String doInBackground(String... urlToSave) { - // Get a handle for the activity. + // Get a handle for the context and activity. + Context context = contextWeakReference.get(); Activity activity = activityWeakReference.get(); // Abort if the activity is gone. @@ -100,8 +106,14 @@ public class SaveUrl extends AsyncTask { // Get the URL from the main activity. URL url = new URL(urlToSave[0]); + // Instantiate the proxy helper. + ProxyHelper proxyHelper = new ProxyHelper(); + + // Get the current proxy. + Proxy proxy = proxyHelper.getCurrentProxy(context); + // Open a connection to the URL. No data is actually sent at this point. - HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(); + HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy); // Add the user agent to the header property. httpUrlConnection.setRequestProperty("User-Agent", userAgent); diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java index dce61690..b978a02d 100644 --- a/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 Soren Stoutner . + * Copyright © 2016-2020 Soren Stoutner . * * This file is part of Privacy Browser . * @@ -23,12 +23,10 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.net.Proxy; import android.net.Uri; import android.os.Build; import android.os.Parcelable; import android.util.ArrayMap; -import android.util.Log; import android.view.View; import androidx.preference.PreferenceManager; @@ -44,6 +42,9 @@ import com.stoutner.privacybrowser.activities.MainWebViewActivity; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.SocketAddress; import java.util.concurrent.Executor; public class ProxyHelper { @@ -210,7 +211,7 @@ public class ProxyHelper { Method onReceiveMethod = receiverClass.getDeclaredMethod("onReceive", Context.class, Intent.class); // Create a proxy change intent. - Intent proxyChangeIntent = new Intent(Proxy.PROXY_CHANGE_ACTION); + Intent proxyChangeIntent = new Intent(android.net.Proxy.PROXY_CHANGE_ACTION); if (Build.VERSION.SDK_INT >= 21) { // Get a proxy info class. @@ -234,9 +235,82 @@ public class ProxyHelper { } } } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) { - Log.d("enableProxyThroughOrbot", "Exception: " + exception); + // Do nothing. } } } } + + public Proxy getCurrentProxy(Context context) { + // Define a proxy variable. + Proxy proxy; + + // Set the proxy according to the current proxy mode + switch (MainWebViewActivity.proxyMode) { + case (ProxyHelper.TOR): + if (Build.VERSION.SDK_INT >= 21) { + // Set the socket address to be localhost port 9050. + SocketAddress torSocketAddress = new InetSocketAddress("localhost", 9050); + + // Set a SOCKS proxy. + proxy = new Proxy(Proxy.Type.SOCKS, torSocketAddress); + } else { + // Set the socket address to be localhost port 8118. + SocketAddress oldTorSocketAddress = new InetSocketAddress("localhost", 8118); + + // Set an HTTP proxy. + proxy = new Proxy(Proxy.Type.HTTP, oldTorSocketAddress); + } + break; + + case (ProxyHelper.I2P): + // Set the socket address to be localhost port 4444. + SocketAddress i2pSocketAddress = new InetSocketAddress("localhost", 4444); + + // Set an HTTP proxy. + proxy = new Proxy(Proxy.Type.HTTP, i2pSocketAddress); + break; + + case (ProxyHelper.CUSTOM): + // Get the shared preferences. + SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); + + // Get the custom proxy URL string. + String customProxyUrlString = sharedPreferences.getString("proxy_custom_url", context.getString(R.string.proxy_custom_url_default_value)); + + // Parse the custom proxy URL. + try { + // Convert the custom proxy URL string to a URI. + Uri customProxyUri = Uri.parse(customProxyUrlString); + + // Set the socket address. + SocketAddress customSocketAddress = new InetSocketAddress(customProxyUri.getHost(), customProxyUri.getPort()); + + // Get the custom proxy scheme. + String customProxyScheme = customProxyUri.getScheme(); + + // Set the proxy according to the scheme. + if ((customProxyScheme != null) && customProxyScheme.startsWith("socks")) { // A SOCKS proxy is specified. + // Set a SOCKS proxy. + proxy = new Proxy(Proxy.Type.SOCKS, customSocketAddress); + } else { // A SOCKS proxy is not specified. + // Set an HTTP proxy. + proxy = new Proxy(Proxy.Type.HTTP, customSocketAddress); + } + } catch (Exception exception) { // The custom proxy cannot be parsed. + // Disable the proxy. + proxy = Proxy.NO_PROXY; + } + break; + + default: // No proxy is in use. + // Set a direct proxy. + proxy = Proxy.NO_PROXY; + break; + + } + + // Return the proxy. + return proxy; + } } \ No newline at end of file -- 2.45.2