Respect proxies when getting source and saving URLs. https://redmine.stoutner.com...
authorSoren Stoutner <soren@stoutner.com>
Sat, 15 Feb 2020 07:32:21 +0000 (00:32 -0700)
committerSoren Stoutner <soren@stoutner.com>
Sat, 15 Feb 2020 07:32:21 +0000 (00:32 -0700)
app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
app/src/main/java/com/stoutner/privacybrowser/activities/ViewSourceActivity.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/GetSource.java
app/src/main/java/com/stoutner/privacybrowser/asynctasks/SaveUrl.java
app/src/main/java/com/stoutner/privacybrowser/helpers/ProxyHelper.java

index 166dc35b6adb28e2d891fd0ff9b1e2a68fb0002d..5e4d3eeb6a5e1360978659fd027e716985c8edf8 100644 (file)
@@ -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();
index 229bcf28ade37a7c7e51beb1ae1d1e7774003a7c..a0f51d14b7a2853f31fd5df68ab08d151f42079b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2017-2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2017-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/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);
         }
     }
 
index c9a9ea33f23be96b25357c75bcdf53f0427d1bbe..415f35bddf6eff65ba299cdd9d2da02852ca691e 100644 (file)
@@ -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<String, Void, SpannableStringBuilder[]> {
-    // Declare a weak reference to the calling activity.
+    // Define weak references to the calling context and activity.
+    private WeakReference<Context> contextWeakReference;
     private WeakReference<Activity> 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<String, Void, SpannableStringBuilder[]>
     // `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<String, Void, SpannableStringBuilder[]>
         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<String, Void, SpannableStringBuilder[]>
             // 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();
index fd72db47463448261a297c7dc93fb20aa32379ec..1eb50323f096afdcfb70fabc64e4564636e89588 100644 (file)
 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<String, Void, String> {
-    // Define a weak reference to the calling activity.
+    // Define a weak references for the calling context and activities.
+    private WeakReference<Context> contextWeakReference;
     private WeakReference<Activity> activityWeakReference;
 
     // Define a success string constant.
@@ -51,8 +55,9 @@ public class SaveUrl extends AsyncTask<String, Void, String> {
     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<String, Void, String> {
 
     @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<String, Void, String> {
             // 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);
index dce61690937c639367b46aea0d267edf3f0b5180..b978a02d48b77759cf291f47f6306c9ffcb49596 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright © 2016-2019 Soren Stoutner <soren@stoutner.com>.
+ * Copyright © 2016-2020 Soren Stoutner <soren@stoutner.com>.
  *
  * This file is part of Privacy Browser <https://www.stoutner.com/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