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