Bump the minimum API to 23. https://redmine.stoutner.com/issues/793
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / helpers / ProxyHelper.kt
1 /*
2  * Copyright © 2016-2021 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
5  *
6  * Privacy Browser is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.helpers
21
22 import android.content.Context
23 import android.content.Intent
24 import android.net.Uri
25 import android.view.View
26
27 import androidx.preference.PreferenceManager
28 import androidx.webkit.ProxyConfig
29 import androidx.webkit.ProxyController
30 import androidx.webkit.WebViewFeature
31
32 import com.stoutner.privacybrowser.R
33 import com.stoutner.privacybrowser.activities.MainWebViewActivity
34
35 import com.google.android.material.snackbar.Snackbar
36
37 import java.lang.Exception
38 import java.lang.IllegalArgumentException
39 import java.net.InetSocketAddress
40 import java.net.Proxy
41 import java.net.SocketAddress
42
43 class ProxyHelper {
44     companion object {
45         // Define the public companion object constants.  These can be moved to public class constants once the entire project has migrated to Kotlin.
46         const val NONE = "None"
47         const val TOR = "Tor"
48         const val I2P = "I2P"
49         const val CUSTOM = "Custom"
50         const val ORBOT_STATUS_ON = "ON"
51     }
52
53     fun setProxy(context: Context, activityView: View, proxyMode: String) {
54         // Create a proxy config builder.
55         val proxyConfigBuilder = ProxyConfig.Builder()
56
57         // Run the commands that correlate to the proxy mode.
58         when (proxyMode) {
59             TOR -> {
60                 // Add the proxy to the builder.  The proxy config builder can use a SOCKS proxy.
61                 proxyConfigBuilder.addProxyRule("socks://localhost:9050")
62
63                 // Ask Orbot to connect if its current status is not `"ON"`.
64                 if (MainWebViewActivity.orbotStatus != ORBOT_STATUS_ON) {
65                     // Create an intent to request Orbot to start.
66                     val orbotIntent = Intent("org.torproject.android.intent.action.START")
67
68                     // Send the intent to the Orbot package.
69                     orbotIntent.setPackage("org.torproject.android")
70
71                     // Request a status response be sent back to this package.
72                     orbotIntent.putExtra("org.torproject.android.intent.extra.PACKAGE_NAME", context.packageName)
73
74                     // Make it so.
75                     context.sendBroadcast(orbotIntent)
76                 }
77             }
78
79             I2P -> {
80                 // Add the proxy to the builder.
81                 proxyConfigBuilder.addProxyRule("http://localhost:4444")
82             }
83
84             CUSTOM -> {
85                 // Get a handle for the shared preferences.
86                 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
87
88                 // Get the custom proxy URL string.
89                 val customProxyUrlString = sharedPreferences.getString(context.getString(R.string.proxy_custom_url_key), context.getString(R.string.proxy_custom_url_default_value))
90
91                 // Parse the custom proxy URL.
92                 try {
93                     // Add the proxy to the builder.
94                     proxyConfigBuilder.addProxyRule(customProxyUrlString!!)
95                 } catch (exception: Exception) {  // The custom proxy URL is invalid.
96                     // Display a Snackbar.
97                     Snackbar.make(activityView, R.string.custom_proxy_invalid, Snackbar.LENGTH_LONG).show()
98                 }
99             }
100         }
101
102         // Apply the proxy settings
103         if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
104             // Convert the proxy config builder into a proxy config.
105             val proxyConfig = proxyConfigBuilder.build()
106
107             // Get the proxy controller.
108             val proxyController = ProxyController.getInstance()
109
110             // Apply the proxy settings.
111             if (proxyMode == NONE) {  // Remove the proxy.  A default executor and runnable are used.
112                 proxyController.clearProxyOverride({}, {})
113             } else {  // Apply the proxy.
114                 try {
115                     // Apply the proxy.  A default executor and runnable are used.
116                     proxyController.setProxyOverride(proxyConfig, {}, {})
117                 } catch (exception: IllegalArgumentException) {  // The proxy config is invalid.
118                     // Display a Snackbar.
119                     Snackbar.make(activityView, R.string.custom_proxy_invalid, Snackbar.LENGTH_LONG).show()
120                 }
121             }
122         }
123     }
124
125     fun getCurrentProxy(context: Context): Proxy {
126         // Get the proxy according to the current proxy mode.
127         val proxy = when (MainWebViewActivity.proxyMode) {
128             TOR -> {
129                 // Use localhost port 9050 as the socket address.
130                 val torSocketAddress: SocketAddress = InetSocketAddress.createUnresolved("localhost", 9050)
131
132                 // Create a SOCKS proxy.
133                 Proxy(Proxy.Type.SOCKS, torSocketAddress)
134             }
135
136             I2P -> {
137                 // Use localhost port 4444 as the socket address.
138                 val i2pSocketAddress: SocketAddress = InetSocketAddress.createUnresolved("localhost", 4444)
139
140                 // Create an HTTP proxy.
141                 Proxy(Proxy.Type.HTTP, i2pSocketAddress)
142             }
143
144             CUSTOM -> {
145                 // Get the shared preferences.
146                 val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
147
148                 // Get the custom proxy URL string.
149                 val customProxyUrlString = sharedPreferences.getString(context.getString(R.string.proxy_custom_url_key), context.getString(R.string.proxy_custom_url_default_value))
150
151                 // Parse the custom proxy URL.
152                 try {
153                     // Convert the custom proxy URL string to a URI.
154                     val customProxyUri = Uri.parse(customProxyUrlString)
155
156                     // Get the custom socket address.
157                     val customSocketAddress: SocketAddress = InetSocketAddress.createUnresolved(customProxyUri.host, customProxyUri.port)
158
159                     // Get the custom proxy scheme.
160                     val customProxyScheme = customProxyUri.scheme
161
162                     // Create a proxy according to the scheme.
163                     if (customProxyScheme != null && customProxyScheme.startsWith("socks")) {  // A SOCKS proxy is specified.
164                         // Create a SOCKS proxy.
165                         Proxy(Proxy.Type.SOCKS, customSocketAddress)
166                     } else {  // A SOCKS proxy is not specified.
167                         // Create an HTTP proxy.
168                         Proxy(Proxy.Type.HTTP, customSocketAddress)
169                     }
170                 } catch (exception: Exception) {  // The custom proxy cannot be parsed.
171                     // Disable the proxy.
172                     Proxy.NO_PROXY
173                 }
174             }
175
176             else -> {
177                 // Create a direct proxy.
178                 Proxy.NO_PROXY
179             }
180         }
181
182         // Return the proxy.
183         return proxy
184     }
185 }