1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.webkit;
18 
19 import androidx.annotation.RequiresFeature;
20 import androidx.annotation.RestrictTo;
21 import androidx.annotation.StringDef;
22 
23 import org.jspecify.annotations.NonNull;
24 
25 import java.lang.annotation.Retention;
26 import java.lang.annotation.RetentionPolicy;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.concurrent.Executor;
31 
32 /**
33  * Config for {@link ProxyController#setProxyOverride(ProxyConfig, Executor, Runnable)}.
34  * <p>
35  * Proxy rules should be added using {@code addProxyRule} methods. Multiple rules can be used as
36  * fallback if a proxy fails to respond (for example, the proxy server is down). Bypass rules can
37  * be set for URLs that should not use these settings.
38  * <p>
39  * For instance, the following code means that WebView would first try to use {@code proxy1.com}
40  * for all URLs, if that fails, {@code proxy2.com}, and if that fails, it would make a direct
41  * connection.
42  * <pre class="prettyprint">
43  * ProxyConfig proxyConfig = new ProxyConfig.Builder().addProxyRule("proxy1.com")
44  *                                                    .addProxyRule("proxy2.com")
45  *                                                    .addDirect()
46  *                                                    .build();
47  * </pre>
48  */
49 public final class ProxyConfig {
50     /**
51      * HTTP scheme.
52      */
53     public static final String MATCH_HTTP = "http";
54     /**
55      * HTTPS scheme.
56      */
57     public static final String MATCH_HTTPS = "https";
58     /**
59      * Matches all schemes.
60      */
61     public static final String MATCH_ALL_SCHEMES = "*";
62     @RestrictTo(RestrictTo.Scope.LIBRARY)
63     @StringDef({MATCH_HTTP, MATCH_HTTPS, MATCH_ALL_SCHEMES})
64     @Retention(RetentionPolicy.SOURCE)
65     public @interface ProxyScheme {}
66     private static final String DIRECT = "direct://";
67     private static final String BYPASS_RULE_SIMPLE_NAMES = "<local>";
68     private static final String BYPASS_RULE_REMOVE_IMPLICIT = "<-loopback>";
69 
70     private final List<ProxyRule> mProxyRules;
71     private final List<String> mBypassRules;
72     private final boolean mReverseBypass;
73 
74     /**
75      */
76     @RestrictTo(RestrictTo.Scope.LIBRARY)
ProxyConfig(@onNull List<ProxyRule> proxyRules, @NonNull List<String> bypassRules, boolean reverseBypass)77     public ProxyConfig(@NonNull List<ProxyRule> proxyRules, @NonNull List<String> bypassRules,
78             boolean reverseBypass) {
79         mProxyRules = proxyRules;
80         mBypassRules = bypassRules;
81         mReverseBypass = reverseBypass;
82     }
83 
84     /**
85      * Returns the current list of proxy rules. Each {@link ProxyRule} object
86      * holds the proxy URL and the URL schemes for which this proxy is used (one of
87      * {@code MATCH_HTTP}, {@code MATCH_HTTPS}, {@code MATCH_ALL_SCHEMES}).
88      *
89      * <p>To add new rules use {@link Builder#addProxyRule(String)} or
90      * {@link Builder#addProxyRule(String, String)}.
91      *
92      * @return List of proxy rules
93      */
getProxyRules()94     public @NonNull List<ProxyRule> getProxyRules() {
95         return Collections.unmodifiableList(mProxyRules);
96     }
97 
98     /**
99      * Returns the current list that holds the bypass rules represented by this object.
100      *
101      * <p>To add new rules use {@link Builder#addBypassRule(String)}.
102      *
103      * @return List of bypass rules
104      */
getBypassRules()105     public @NonNull List<String> getBypassRules() {
106         return Collections.unmodifiableList(mBypassRules);
107     }
108 
109     /**
110      * Returns {@code true} if reverse bypass is enabled. Reverse bypass means that only URLs in the
111      * bypass list will use these proxy settings. {@link #getBypassRules()} returns the URL list.
112      *
113      * <p>See {@link Builder#setReverseBypassEnabled(boolean)} for a more detailed description.
114      *
115      * @return reverseBypass
116      *
117      */
isReverseBypassEnabled()118     public boolean isReverseBypassEnabled() {
119         return mReverseBypass;
120     }
121 
122     /**
123      * Class that holds a scheme filter and a proxy URL.
124      */
125     public static final class ProxyRule {
126         private final String mSchemeFilter;
127         private final String mUrl;
128 
129         /**
130          */
131         @RestrictTo(RestrictTo.Scope.LIBRARY)
ProxyRule(@onNull String schemeFilter, @NonNull String url)132         public ProxyRule(@NonNull String schemeFilter, @NonNull String url) {
133             mSchemeFilter = schemeFilter;
134             mUrl = url;
135         }
136 
137         /**
138          */
139         @RestrictTo(RestrictTo.Scope.LIBRARY)
ProxyRule(@onNull String url)140         public ProxyRule(@NonNull String url) {
141             this(ProxyConfig.MATCH_ALL_SCHEMES, url);
142         }
143 
144         /**
145          * Returns the {@link String} that represents the scheme filter for this object.
146          *
147          * @return Scheme filter
148          */
getSchemeFilter()149         public @NonNull String getSchemeFilter() {
150             return mSchemeFilter;
151         }
152 
153         /**
154          * Returns the {@link String} that represents the proxy URL for this object.
155          *
156          * @return Proxy URL
157          */
getUrl()158         public @NonNull String getUrl() {
159             return mUrl;
160         }
161     }
162 
163     /**
164      * ProxyConfig builder. Use {@link Builder#addProxyRule(String)} or
165      * {@link Builder#addProxyRule(String, String)} to add proxy rules. Use
166      * {@link Builder#addBypassRule(String)} to add bypass rules. Use {@link Builder#build()} to
167      * build this into a {@link ProxyConfig} object.
168      *
169      * <p class="note"><b>Note:</b> applying a {@code ProxyConfig} with no rules will cause all
170      * connections to be made directly.
171      */
172     public static final class Builder {
173         private final List<ProxyRule> mProxyRules;
174         private final List<String> mBypassRules;
175         private boolean mReverseBypass = false;
176 
177         /**
178          * Create an empty ProxyConfig Builder.
179          */
Builder()180         public Builder() {
181             mProxyRules = new ArrayList<>();
182             mBypassRules = new ArrayList<>();
183         }
184 
185         /**
186          * Create a ProxyConfig Builder from an existing ProxyConfig object.
187          */
Builder(@onNull ProxyConfig proxyConfig)188         public Builder(@NonNull ProxyConfig proxyConfig) {
189             mProxyRules = proxyConfig.getProxyRules();
190             mBypassRules = proxyConfig.getBypassRules();
191             mReverseBypass = proxyConfig.isReverseBypassEnabled();
192         }
193 
194         /**
195          * Builds the current rules into a ProxyConfig object.
196          *
197          * @return The ProxyConfig object represented by this Builder
198          */
build()199         public @NonNull ProxyConfig build() {
200             return new ProxyConfig(proxyRules(), bypassRules(), reverseBypass());
201         }
202 
203         /**
204          * Adds a proxy to be used for all URLs. This method can be called multiple times to add
205          * multiple rules. Additional rules have decreasing precedence.
206          * <p>Proxy is a string in the format {@code [scheme://]host[:port]}. Scheme is optional, if
207          * present must be {@code HTTP}, {@code HTTPS} or
208          * <a href="https://tools.ietf.org/html/rfc1928">SOCKS</a> and defaults to {@code HTTP}.
209          * Host is one of an IPv6 literal with brackets, an IPv4 literal or one or more labels
210          * separated by a period. Port number is optional and defaults to {@code 80} for
211          * {@code HTTP}, {@code 443} for {@code HTTPS} and {@code 1080} for {@code SOCKS}.
212          * <p>
213          * The correct syntax for hosts is defined by
214          * <a href="https://tools.ietf.org/html/rfc3986#section-3.2.2">RFC 3986</a>
215          * <p>
216          * Examples:
217          * <table>
218          * <tr><th> Scheme </th> <th> Host </th> <th> Port </th> <th> Proxy URL </th></tr>
219          * <tr><td></td> <td>example.com</td> <td></td> <td>example.com</td> </tr>
220          * <tr><td>https</td> <td>example.com</td> <td></td> <td>https://example.com</td> </tr>
221          * <tr><td></td> <td>example.com</td> <td>1111</td> <td>example.com:1111</td> </tr>
222          * <tr><td>https</td> <td>example.com</td> <td>1111</td> <td>https://example.com:1111</td> </tr>
223          * <tr><td></td> <td>192.168.1.1</td> <td></td> <td>192.168.1.1</td> </tr>
224          * <tr><td></td> <td>192.168.1.1</td> <td>2020</td> <td>192.168.1.1:2020</td> </tr>
225          * <tr><td></td> <td>[10:20:30:40:50:60:70:80]</td>
226          * <td></td> <td>[10:20:30:40:50:60:70:80]</td> </tr>
227          * </table>
228          *
229          * @param proxyUrl Proxy URL
230          * @return This Builder object
231          */
addProxyRule(@onNull String proxyUrl)232         public @NonNull Builder addProxyRule(@NonNull String proxyUrl) {
233             mProxyRules.add(new ProxyRule(proxyUrl));
234             return this;
235         }
236 
237         /**
238          * This does everything that {@link Builder#addProxyRule(String)} does,
239          * but only applies to URLs using {@code schemeFilter}. Scheme filter must be one of
240          * {@link ProxyConfig#MATCH_HTTP}, {@link ProxyConfig#MATCH_HTTPS} or
241          * {@link ProxyConfig#MATCH_ALL_SCHEMES}.
242          *
243          * @param proxyUrl Proxy URL
244          * @param schemeFilter Scheme filter
245          * @return This Builder object
246          */
addProxyRule(@onNull String proxyUrl, @ProxyScheme @NonNull String schemeFilter)247         public @NonNull Builder addProxyRule(@NonNull String proxyUrl,
248                 @ProxyScheme @NonNull String schemeFilter) {
249             mProxyRules.add(new ProxyRule(schemeFilter, proxyUrl));
250             return this;
251         }
252 
253         /**
254          * Adds a new bypass rule that describes URLs that should skip proxy override settings
255          * and make a direct connection instead. These can be URLs or IP addresses. Wildcards are
256          * accepted. For instance, the rule {@code "*example.com"} would mean that requests to
257          * {@code "http://example.com"} and {@code "www.example.com"} would not be directed to any
258          * proxy, instead, would be made directly to the origin specified by the URL.
259          *
260          * @param bypassRule Rule to be added to the exclusion list
261          * @return This Builder object
262          */
addBypassRule(@onNull String bypassRule)263         public @NonNull Builder addBypassRule(@NonNull String bypassRule) {
264             mBypassRules.add(bypassRule);
265             return this;
266         }
267 
268         /**
269          * Adds a proxy rule so URLs that match the scheme filter are connected to directly instead
270          * of using a proxy server.
271          *
272          * @param schemeFilter Scheme filter
273          * @return This Builder object
274          */
addDirect(@roxyScheme @onNull String schemeFilter)275         public @NonNull Builder addDirect(@ProxyScheme @NonNull String schemeFilter) {
276             mProxyRules.add(new ProxyRule(schemeFilter, DIRECT));
277             return this;
278         }
279 
280         /**
281          * Adds a proxy rule so URLs are connected to directly instead of using a proxy server.
282          *
283          * @return This Builder object
284          */
addDirect()285         public @NonNull Builder addDirect() {
286             return addDirect(MATCH_ALL_SCHEMES);
287         }
288 
289         /**
290          * Hostnames without a period in them (and that are not IP literals) will skip proxy
291          * settings and be connected to directly instead. Examples: {@code "abc"}, {@code "local"},
292          * {@code "some-domain"}.
293          * <p>
294          * Hostnames with a trailing dot are not considered simple by this definition.
295          *
296          * @return This Builder object
297          */
bypassSimpleHostnames()298         public @NonNull Builder bypassSimpleHostnames() {
299             return addBypassRule(BYPASS_RULE_SIMPLE_NAMES);
300         }
301 
302         /**
303          * By default, certain hostnames implicitly bypass the proxy if they are link-local IPs, or
304          * localhost addresses. For instance hostnames matching any of (non-exhaustive list):
305          * <ul>
306          * <li>localhost</li>
307          * <li>*.localhost</li>
308          * <li>[::1]</li>
309          * <li>127.0.0.1/8</li>
310          * <li>169.254/16</li>
311          * <li>[FE80::]/10</li>
312          * </ul>
313          * <p>
314          * Call this function to override the default behavior and force localhost and link-local
315          * URLs to be sent through the proxy.
316          *
317          * @return This Builder object
318          */
removeImplicitRules()319         public @NonNull Builder removeImplicitRules() {
320             return addBypassRule(BYPASS_RULE_REMOVE_IMPLICIT);
321         }
322 
323         /**
324          * Reverse the bypass list.
325          *
326          * <p>The default value is {@code false}, in which case all URLs will use proxy settings
327          * except the ones in the bypass list, which will be connected to directly instead.
328          *
329          * <p>If set to {@code true}, then only URLs in the bypass list will use these proxy
330          * settings, and all other URLs will be connected to directly.
331          *
332          * <p>Use {@link #addBypassRule(String)} to add bypass rules.
333          *
334          * <p>This method should only be called if
335          * {@link WebViewFeature#isFeatureSupported(String)}
336          * returns {@code true} for {@link WebViewFeature#PROXY_OVERRIDE_REVERSE_BYPASS}.
337          *
338          * @return This Builder object
339          */
340         @RequiresFeature(name = WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS,
341                 enforcement = "androidx.webkit.WebViewFeature#isFeatureSupported")
setReverseBypassEnabled(boolean reverseBypass)342         public @NonNull Builder setReverseBypassEnabled(boolean reverseBypass) {
343             mReverseBypass = reverseBypass;
344             return this;
345         }
346 
proxyRules()347         private @NonNull List<ProxyRule> proxyRules() {
348             return mProxyRules;
349         }
350 
bypassRules()351         private @NonNull List<String> bypassRules() {
352             return mBypassRules;
353         }
354 
reverseBypass()355         private boolean reverseBypass() {
356             return mReverseBypass;
357         }
358     }
359 }
360