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 static androidx.webkit.ProxyConfig.MATCH_ALL_SCHEMES; 20 import static androidx.webkit.ProxyConfig.MATCH_HTTP; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.fail; 25 26 import androidx.concurrent.futures.ResolvableFuture; 27 import androidx.test.ext.junit.runners.AndroidJUnit4; 28 import androidx.test.filters.MediumTest; 29 import androidx.webkit.internal.ProxyControllerImpl; 30 import androidx.webkit.test.common.WebViewOnUiThread; 31 import androidx.webkit.test.common.WebkitUtils; 32 33 import org.junit.After; 34 import org.junit.Assert; 35 import org.junit.Before; 36 import org.junit.Test; 37 import org.junit.runner.RunWith; 38 39 import java.io.IOException; 40 import java.util.concurrent.Executor; 41 import java.util.concurrent.TimeUnit; 42 43 import okhttp3.mockwebserver.MockWebServer; 44 45 @MediumTest 46 @RunWith(AndroidJUnit4.class) 47 public class ProxyControllerTest { 48 private WebViewOnUiThread mWebViewOnUiThread; 49 private MockWebServer mContentServer; 50 private MockWebServer mProxyServer; 51 52 @Before setUp()53 public void setUp() throws IOException { 54 WebkitUtils.checkFeature(WebViewFeature.PROXY_OVERRIDE); 55 56 mWebViewOnUiThread = new WebViewOnUiThread(); 57 mContentServer = new MockWebServer(); 58 mProxyServer = new MockWebServer(); 59 mContentServer.start(); 60 mProxyServer.start(); 61 } 62 63 @After tearDown()64 public void tearDown() throws Exception { 65 if (!WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) return; 66 67 clearProxyOverrideSync(); 68 if (mWebViewOnUiThread != null) { 69 mWebViewOnUiThread.cleanUp(); 70 } 71 if (mContentServer != null) { 72 mContentServer.shutdown(); 73 } 74 if (mProxyServer != null) { 75 mProxyServer.shutdown(); 76 } 77 } 78 79 /** 80 * This test should have an equivalent in CTS when this is implemented in the framework. 81 * 82 * Tests that ClearProxyOverride callback is called even if there is no settings to clear. 83 */ 84 @Test testCallbacks()85 public void testCallbacks() throws Exception { 86 // Test setProxyOverride's callback 87 setProxyOverrideSync(new ProxyConfig.Builder().build()); 88 // Test clearProxyOverride's callback with a proxy override setting 89 clearProxyOverrideSync(); 90 // Test clearProxyOverride's callback without a proxy override setting 91 clearProxyOverrideSync(); 92 // If we got to this point it means all callbacks were called as expected 93 } 94 95 /** 96 * This test should have an equivalent in CTS when this is implemented in the framework. 97 */ 98 @Test testProxyOverride()99 public void testProxyOverride() throws Exception { 100 final String contentUrl = mContentServer.url("/").toString(); 101 final String proxyUrl = mProxyServer.getHostName() + ":" + mProxyServer.getPort(); 102 103 // Clear proxy override and load content url 104 clearProxyOverrideSync(); 105 mWebViewOnUiThread.loadUrl(contentUrl); 106 assertNotNull(mContentServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS, 107 TimeUnit.MILLISECONDS)); 108 int contentServerRequestCount = mContentServer.getRequestCount(); 109 110 // Set proxy override and load content url 111 // Localhost should use proxy with loopback rule 112 setProxyOverrideSync(new ProxyConfig.Builder() 113 .addProxyRule(proxyUrl) 114 .removeImplicitRules() 115 .build()); 116 mWebViewOnUiThread.loadUrl(contentUrl); 117 assertNotNull(mProxyServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS, 118 TimeUnit.MILLISECONDS)); 119 int proxyServerRequestCount = mProxyServer.getRequestCount(); 120 assertEquals(contentServerRequestCount, mContentServer.getRequestCount()); 121 122 // Clear proxy override and load content url 123 clearProxyOverrideSync(); 124 mWebViewOnUiThread.loadUrl(contentUrl); 125 assertNotNull(mContentServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS, 126 TimeUnit.MILLISECONDS)); 127 assertEquals(proxyServerRequestCount, mProxyServer.getRequestCount()); 128 } 129 130 /** 131 * This test should have an equivalent in CTS when this is implemented in the framework. 132 */ 133 @Test testProxyOverrideLocalhost()134 public void testProxyOverrideLocalhost() throws Exception { 135 final String contentUrl = mContentServer.url("/").toString(); 136 int proxyServerRequestCount = mProxyServer.getRequestCount(); 137 138 // Set proxy override and load content url 139 // Localhost should not use proxy settings 140 setProxyOverrideSync(new ProxyConfig.Builder() 141 .addProxyRule(mProxyServer.getHostName() + ":" + mProxyServer.getPort()) 142 .build()); 143 mWebViewOnUiThread.loadUrl(contentUrl); 144 assertNotNull(mContentServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS, 145 TimeUnit.MILLISECONDS)); 146 assertEquals(proxyServerRequestCount, mProxyServer.getRequestCount()); 147 } 148 149 /** 150 * This test should have an equivalent in CTS when this is implemented in the framework. 151 */ 152 @Test testReverseBypass()153 public void testReverseBypass() throws Exception { 154 WebkitUtils.checkFeature(WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS); 155 156 final String contentUrl = "http://www.example.com"; 157 final String bypassUrl = "www.example.com"; 158 int proxyServerRequestCount = mProxyServer.getRequestCount(); 159 160 // Set proxy override with reverse bypass and load content url 161 // The content url (in the bypass list) should use proxy settings. 162 setProxyOverrideSync(new ProxyConfig.Builder() 163 .addProxyRule(mProxyServer.getHostName() + ":" + mProxyServer.getPort()) 164 .addBypassRule(bypassUrl) 165 .setReverseBypassEnabled(true) 166 .build()); 167 mWebViewOnUiThread.loadUrl(contentUrl); 168 169 proxyServerRequestCount++; 170 assertNotNull(mProxyServer.takeRequest(WebkitUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)); 171 assertEquals(proxyServerRequestCount, mProxyServer.getRequestCount()); 172 } 173 174 /** 175 * This test should have an equivalent in CTS when this is implemented in the framework. 176 * 177 * Enumerates valid patterns to check they are supported. 178 */ 179 @Test testValidInput()180 public void testValidInput() throws Exception { 181 ProxyConfig validRules = new ProxyConfig.Builder() 182 .addProxyRule("direct://") 183 .addProxyRule("www.example.com") 184 .addProxyRule("http://www.example.com") 185 .addProxyRule("https://www.example.com") 186 .addProxyRule("www.example.com:123") 187 .addProxyRule("http://www.example.com:123") 188 .addProxyRule("10.0.0.1") 189 .addProxyRule("10.0.0.1:123") 190 .addProxyRule("http://10.0.0.1") 191 .addProxyRule("https://10.0.0.1") 192 .addProxyRule("http://10.0.0.1:123") 193 .addProxyRule("[FE80:CD00:0000:0CDE:1257:0000:211E:729C]") 194 .addProxyRule("[FE80:CD00:0:CDE:1257:0:211E:729C]") 195 .addBypassRule("www.rule.com") 196 .addBypassRule("*.rule.com") 197 .addBypassRule("*rule.com") 198 .addBypassRule("www.*.com") 199 .addBypassRule("www.rule*") 200 .build(); 201 setProxyOverrideSync(validRules); 202 // If we got to this point it means our input was accepted as expected 203 } 204 205 /** 206 * This test should have an equivalent in CTS when this is implemented in the framework. 207 */ 208 @Test testInvalidProxyUrls()209 public void testInvalidProxyUrls() throws Exception { 210 String[] invalidProxyUrls = { 211 null, 212 "", // empty 213 " ", // spaces only 214 "dddf:", // bad port 215 "dddd:d", // bad port 216 "http://", // no valid host/port 217 "http:/", // ambiguous, will fail due to bad port 218 "http:", // ambiguous, will fail due to bad port 219 "direct://xyz", // direct shouldn't have host/port 220 }; 221 222 for (String proxyUrl : invalidProxyUrls) { 223 try { 224 setProxyOverrideSync(new ProxyConfig.Builder() 225 .addProxyRule(proxyUrl).build()); 226 fail("No exception for invalid proxy url: " + proxyUrl); 227 } catch (IllegalArgumentException e) { 228 // Expected 229 } 230 } 231 } 232 233 /** 234 * This test should have an equivalent in CTS when this is implemented in the framework. 235 */ 236 @Test testInvalidBypassRules()237 public void testInvalidBypassRules() throws Exception { 238 String[] invalidBypassRules = { 239 null, 240 "", // empty 241 "http://", // no valid host/port 242 "20:example.com", // bad port 243 "example.com:-20" // bad port 244 }; 245 246 for (String bypassRule : invalidBypassRules) { 247 try { 248 setProxyOverrideSync(new ProxyConfig.Builder() 249 .addBypassRule(bypassRule).build()); 250 fail("No exception for invalid bypass rule: " + bypassRule); 251 } catch (IllegalArgumentException e) { 252 // Expected 253 } 254 } 255 } 256 257 @Test testProxyRulesToArray()258 public void testProxyRulesToArray() throws Exception { 259 String[][] actual; 260 String[][] expected; 261 262 actual = ProxyControllerImpl.proxyRulesToStringArray( 263 new ProxyConfig.Builder().build().getProxyRules()); 264 expected = new String[][]{}; 265 Assert.assertArrayEquals(expected, actual); 266 267 actual = ProxyControllerImpl.proxyRulesToStringArray( 268 new ProxyConfig.Builder().addProxyRule("proxy1.com").build().getProxyRules()); 269 expected = new String[][]{{MATCH_ALL_SCHEMES, "proxy1.com"}}; 270 Assert.assertArrayEquals(expected, actual); 271 272 actual = ProxyControllerImpl.proxyRulesToStringArray( 273 new ProxyConfig.Builder().addProxyRule("proxy1.com").addProxyRule( 274 "proxy2.com").build().getProxyRules()); 275 expected = new String[][]{{MATCH_ALL_SCHEMES, "proxy1.com"}, 276 {MATCH_ALL_SCHEMES, "proxy2.com"}}; 277 Assert.assertArrayEquals(expected, actual); 278 279 actual = ProxyControllerImpl.proxyRulesToStringArray( 280 new ProxyConfig.Builder().addProxyRule("proxy1.com").addProxyRule("proxy2.com", 281 MATCH_HTTP).build().getProxyRules()); 282 expected = new String[][]{{MATCH_ALL_SCHEMES, "proxy1.com"}, {MATCH_HTTP, "proxy2.com"}}; 283 Assert.assertArrayEquals(expected, actual); 284 } 285 setProxyOverrideSync(final ProxyConfig proxyRules)286 private void setProxyOverrideSync(final ProxyConfig proxyRules) { 287 final ResolvableFuture<Void> future = ResolvableFuture.create(); 288 ProxyController.getInstance().setProxyOverride(proxyRules, new SynchronousExecutor(), 289 () -> future.set(null)); 290 // This future is used to ensure that setProxyOverride's callback was called 291 WebkitUtils.waitForFuture(future); 292 } 293 clearProxyOverrideSync()294 private void clearProxyOverrideSync() { 295 final ResolvableFuture<Void> future = ResolvableFuture.create(); 296 ProxyController.getInstance().clearProxyOverride( 297 new SynchronousExecutor(), () -> future.set(null)); 298 // This future is used to ensure that clearProxyOverride's callback was called 299 WebkitUtils.waitForFuture(future); 300 } 301 302 static class SynchronousExecutor implements Executor { 303 @Override execute(Runnable r)304 public void execute(Runnable r) { 305 r.run(); 306 } 307 } 308 } 309