| /* |
| * Copyright 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.webkit; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| |
| |
| import android.os.Build; |
| import android.webkit.CookieManager; |
| import android.webkit.WebResourceRequest; |
| import android.webkit.WebResourceResponse; |
| import android.webkit.WebSettings; |
| import android.webkit.WebView; |
| |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.SdkSuppress; |
| import androidx.test.filters.SmallTest; |
| |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.io.ByteArrayInputStream; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| @SmallTest |
| @RunWith(AndroidJUnit4.class) |
| @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) |
| public class WebSettingsCompatUserAgentMetadataTest { |
| private static final String[] USER_AGENT_CLIENT_HINTS = {"sec-ch-ua", "sec-ch-ua-arch", |
| "sec-ch-ua-platform", "sec-ch-ua-model", "sec-ch-ua-mobile", "sec-ch-ua-full-version", |
| "sec-ch-ua-platform-version", "sec-ch-ua-bitness", "sec-ch-ua-full-version-list", |
| "sec-ch-ua-wow64"}; |
| |
| private static final String FIRST_URL = "/first.html"; |
| private static final String SECOND_URL = "/second.html"; |
| |
| private static final String FIRST_RAW_HTML = |
| "<!DOCTYPE html>\n" |
| + "<html>\n" |
| + " <body>\n" |
| + " <iframe src=\"" + SECOND_URL + "\">\n" |
| + " </iframe>\n" |
| + " </body>\n" |
| + "</html>\n"; |
| |
| private static final String SECOND_RAW_HTML = |
| "<!DOCTYPE html>\n" |
| + "<html>\n" |
| + " <body>\n" |
| + " </body>\n" |
| + "</html>\n"; |
| |
| private WebViewOnUiThread mWebViewOnUiThread; |
| private TestHttpsWebViewClient mTestHttpsWebViewClient; |
| |
| @Before |
| public void setUp() throws Exception { |
| mWebViewOnUiThread = new androidx.webkit.WebViewOnUiThread(); |
| mTestHttpsWebViewClient = new TestHttpsWebViewClient(mWebViewOnUiThread); |
| } |
| |
| @After |
| public void tearDown() { |
| if (mWebViewOnUiThread != null) { |
| mWebViewOnUiThread.cleanUp(); |
| } |
| CookieManager.getInstance().removeAllCookies(null); |
| } |
| |
| /** |
| * A WebViewClient to intercept the request to mock HTTPS response. |
| */ |
| private static final class TestHttpsWebViewClient extends |
| WebViewOnUiThread.WaitForLoadedClient { |
| private final List<WebResourceRequest> mInterceptedRequests = new ArrayList<>(); |
| TestHttpsWebViewClient(WebViewOnUiThread webViewOnUiThread) throws Exception { |
| super(webViewOnUiThread); |
| } |
| |
| @Override |
| public WebResourceResponse shouldInterceptRequest(WebView view, |
| WebResourceRequest request) { |
| // Only return content for FIRST_URL and SECOND_URL, deny all other requests. |
| Map<String, String> responseHeaders = new HashMap<>(); |
| responseHeaders.put("Content-Type", "text/html"); |
| responseHeaders.put("Accept-Ch", String.join(",", USER_AGENT_CLIENT_HINTS)); |
| |
| if (request.getUrl().toString().endsWith(FIRST_URL)) { |
| mInterceptedRequests.add(request); |
| return new WebResourceResponse("text/html", "utf-8", |
| 200, "OK", responseHeaders, |
| new ByteArrayInputStream( |
| FIRST_RAW_HTML.getBytes(StandardCharsets.UTF_8))); |
| } else if (request.getUrl().toString().endsWith(SECOND_URL)) { |
| mInterceptedRequests.add(request); |
| return new WebResourceResponse("text/html", "utf-8", |
| 200, "OK", responseHeaders, |
| new ByteArrayInputStream( |
| SECOND_RAW_HTML.getBytes(StandardCharsets.UTF_8))); |
| } |
| return new WebResourceResponse("text/html", "UTF-8", null); |
| } |
| |
| public List<WebResourceRequest> getInterceptedRequests() { |
| return mInterceptedRequests; |
| } |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataDefault() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| UserAgentMetadata defaultSetting = WebSettingsCompat.getUserAgentMetadata( |
| settings); |
| // Check brand version list. |
| List<String> brands = new ArrayList<>(); |
| Assert.assertNotNull(defaultSetting.getBrandVersionList()); |
| for (UserAgentMetadata.BrandVersion bv : defaultSetting.getBrandVersionList()) { |
| brands.add(bv.getBrand()); |
| } |
| Assert.assertTrue("The default brand should contains Android WebView.", |
| brands.contains("Android WebView")); |
| // Check platform, bitness and wow64. |
| assertEquals("The default platform is Android.", "Android", |
| defaultSetting.getPlatform()); |
| assertEquals("The default bitness is 0.", UserAgentMetadata.BITNESS_DEFAULT, |
| defaultSetting.getBitness()); |
| assertFalse("The default wow64 is false.", defaultSetting.isWow64()); |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataExplicitlyDefault() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| UserAgentMetadata uaMetadata = new UserAgentMetadata.Builder() |
| .setBrandVersionList(new ArrayList<>()) |
| .setArchitecture(null) |
| .setFullVersion(null) |
| .setPlatform(null) |
| .setPlatformVersion(null) |
| .setModel(null).build(); |
| WebSettingsCompat.setUserAgentMetadata(settings, uaMetadata); |
| |
| UserAgentMetadata userAgentMetadata = WebSettingsCompat.getUserAgentMetadata(settings); |
| // Check brand version list. |
| List<String> brands = new ArrayList<>(); |
| Assert.assertNotNull(userAgentMetadata.getBrandVersionList()); |
| for (UserAgentMetadata.BrandVersion bv : userAgentMetadata.getBrandVersionList()) { |
| brands.add(bv.getBrand()); |
| } |
| Assert.assertTrue("The default brand should contains Android WebView.", |
| brands.contains("Android WebView")); |
| assertEquals("The default platform is Android.", "Android", |
| userAgentMetadata.getPlatform()); |
| assertNotNull(userAgentMetadata.getArchitecture()); |
| assertNotNull(userAgentMetadata.getFullVersion()); |
| assertNotNull(userAgentMetadata.getPlatform()); |
| assertNotNull(userAgentMetadata.getPlatformVersion()); |
| assertNotNull(userAgentMetadata.getModel()); |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataDefaultHttpHeader() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| mWebViewOnUiThread.setWebViewClient(mTestHttpsWebViewClient); |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| settings.setJavaScriptEnabled(true); |
| |
| // As WebViewOnUiThread clear cache doesn't work well, using different origin to avoid |
| // client hints cache impacts other tests. |
| String baseUrl = "https://example1.com"; |
| mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL); |
| List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests(); |
| Assert.assertEquals(2, requests.size()); |
| |
| // Make sure the first request has low-entropy client hints. |
| WebResourceRequest recordedRequest = requests.get(0); |
| Assert.assertEquals(baseUrl + FIRST_URL, recordedRequest.getUrl().toString()); |
| Map<String, String> requestHeaders = recordedRequest.getRequestHeaders(); |
| Assert.assertTrue( |
| requestHeaders.get("sec-ch-ua").contains("Android WebView")); |
| Assert.assertFalse(requestHeaders.get("sec-ch-ua-mobile").isEmpty()); |
| Assert.assertEquals("\"Android\"", |
| requestHeaders.get("sec-ch-ua-platform")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-platform-version")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-arch")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-full-version-list")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-bitness")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-model")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-wow64")); |
| |
| // Verify all user-agent client hints on the second request. |
| recordedRequest = requests.get(1); |
| Assert.assertEquals(baseUrl + SECOND_URL, recordedRequest.getUrl().toString()); |
| requestHeaders = recordedRequest.getRequestHeaders(); |
| |
| Assert.assertTrue( |
| requestHeaders.get("sec-ch-ua").contains("Android WebView")); |
| Assert.assertFalse(requestHeaders.get("sec-ch-ua-mobile").isEmpty()); |
| Assert.assertEquals("\"Android\"", |
| requestHeaders.get("sec-ch-ua-platform")); |
| Assert.assertFalse(requestHeaders.get("sec-ch-ua-platform-version").isEmpty()); |
| Assert.assertFalse(requestHeaders.get("sec-ch-ua-arch").isEmpty()); |
| Assert.assertFalse(requestHeaders.get("sec-ch-ua-full-version-list").isEmpty()); |
| Assert.assertEquals("\"\"", requestHeaders.get("sec-ch-ua-bitness")); |
| Assert.assertFalse(requestHeaders.get("sec-ch-ua-model").isEmpty()); |
| Assert.assertEquals("?0", requestHeaders.get("sec-ch-ua-wow64")); |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataFullOverrides() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| // Overrides user-agent metadata. |
| UserAgentMetadata.BrandVersion brandVersion = new UserAgentMetadata.BrandVersion.Builder() |
| .setBrand("myBrand").setMajorVersion("1").setFullVersion("1.1.1.1").build(); |
| UserAgentMetadata overrideSetting = new UserAgentMetadata.Builder() |
| .setBrandVersionList(Collections.singletonList(brandVersion)) |
| .setFullVersion("1.1.1.1") |
| .setPlatform("myPlatform").setPlatformVersion("2.2.2.2").setArchitecture("myArch") |
| .setMobile(true).setModel("myModel").setBitness(32) |
| .setWow64(false).build(); |
| |
| WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting); |
| assertEquals( |
| "After override set the user-agent metadata, it should be returned", |
| overrideSetting, WebSettingsCompat.getUserAgentMetadata( |
| settings)); |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataFullOverridesHttpHeader() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| mWebViewOnUiThread.setWebViewClient(mTestHttpsWebViewClient); |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| settings.setJavaScriptEnabled(true); |
| |
| // Overrides user-agent metadata. |
| UserAgentMetadata.BrandVersion brandVersion = new UserAgentMetadata.BrandVersion.Builder() |
| .setBrand("myBrand").setMajorVersion("1").setFullVersion("1.1.1.1").build(); |
| UserAgentMetadata overrideSetting = new UserAgentMetadata.Builder() |
| .setBrandVersionList(Collections.singletonList(brandVersion)) |
| .setFullVersion("1.1.1.1").setPlatform("myPlatform") |
| .setPlatformVersion("2.2.2.2").setArchitecture("myArch") |
| .setMobile(true).setModel("myModel").setBitness(32) |
| .setWow64(false).build(); |
| WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting); |
| |
| // As WebViewOnUiThread clear cache doesn't work well, using different origin to avoid |
| // client hints cache impacts other tests. |
| String baseUrl = "https://example2.com"; |
| mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL); |
| List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests(); |
| Assert.assertEquals(2, requests.size()); |
| |
| // Make sure the first request has low-entropy client hints. |
| WebResourceRequest recordedRequest = requests.get(0); |
| Assert.assertEquals(baseUrl + FIRST_URL, recordedRequest.getUrl().toString()); |
| Map<String, String> requestHeaders = recordedRequest.getRequestHeaders(); |
| Assert.assertEquals("\"myBrand\";v=\"1\"", |
| requestHeaders.get("sec-ch-ua")); |
| Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile")); |
| Assert.assertEquals("\"myPlatform\"", |
| requestHeaders.get("sec-ch-ua-platform")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-platform-version")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-arch")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-full-version-list")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-bitness")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-model")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-wow64")); |
| |
| // Verify all user-agent client hints on the second request. |
| recordedRequest = requests.get(1); |
| Assert.assertEquals(baseUrl + SECOND_URL, recordedRequest.getUrl().toString()); |
| requestHeaders = recordedRequest.getRequestHeaders(); |
| |
| Assert.assertEquals("\"myBrand\";v=\"1\"", |
| requestHeaders.get("sec-ch-ua")); |
| Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile")); |
| Assert.assertEquals("\"myPlatform\"", |
| requestHeaders.get("sec-ch-ua-platform")); |
| Assert.assertEquals("\"2.2.2.2\"", |
| requestHeaders.get("sec-ch-ua-platform-version")); |
| Assert.assertEquals("\"myArch\"", |
| requestHeaders.get("sec-ch-ua-arch")); |
| Assert.assertEquals("\"myBrand\";v=\"1.1.1.1\"", |
| requestHeaders.get("sec-ch-ua-full-version-list")); |
| Assert.assertEquals("\"32\"", |
| requestHeaders.get("sec-ch-ua-bitness")); |
| Assert.assertEquals("\"myModel\"", |
| requestHeaders.get("sec-ch-ua-model")); |
| Assert.assertEquals("?0", requestHeaders.get("sec-ch-ua-wow64")); |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataPartialOverride() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| // Overrides without setting user-agent metadata platform and bitness. |
| UserAgentMetadata.BrandVersion brandVersion = new UserAgentMetadata.BrandVersion.Builder() |
| .setBrand("myBrand").setMajorVersion("1").setFullVersion("1.1.1.1").build(); |
| UserAgentMetadata overrideSetting = new UserAgentMetadata.Builder() |
| .setBrandVersionList(Collections.singletonList(brandVersion)) |
| .setFullVersion("1.1.1.1") |
| .setPlatformVersion("2.2.2.2").setArchitecture("myArch").setMobile(true) |
| .setModel("myModel").setWow64(false).build(); |
| |
| WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting); |
| UserAgentMetadata actualSetting = WebSettingsCompat.getUserAgentMetadata( |
| settings); |
| assertEquals("Platform should reset to system default if no overrides.", |
| "Android", actualSetting.getPlatform()); |
| assertEquals("Bitness should reset to system default if no overrides.", |
| UserAgentMetadata.BITNESS_DEFAULT, actualSetting.getBitness()); |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataPartialOverrideHttpHeader() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| mWebViewOnUiThread.setWebViewClient(mTestHttpsWebViewClient); |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| settings.setJavaScriptEnabled(true); |
| |
| // Overrides without setting user-agent metadata platform and bitness. |
| UserAgentMetadata.BrandVersion brandVersion = new UserAgentMetadata.BrandVersion.Builder() |
| .setBrand("myBrand").setMajorVersion("1").setFullVersion("1.1.1.1").build(); |
| UserAgentMetadata overrideSetting = new UserAgentMetadata.Builder() |
| .setBrandVersionList(Collections.singletonList(brandVersion)) |
| .setFullVersion("1.1.1.1").setPlatformVersion("2.2.2.2") |
| .setArchitecture("myArch").setMobile(true).setModel("myModel").setWow64(false) |
| .build(); |
| WebSettingsCompat.setUserAgentMetadata(settings, overrideSetting); |
| |
| // As WebViewOnUiThread clear cache doesn't work well, using different origin to avoid |
| // client hints cache impacts other tests. |
| String baseUrl = "https://example3.com"; |
| mWebViewOnUiThread.loadUrlAndWaitForCompletion(baseUrl + FIRST_URL); |
| mWebViewOnUiThread.loadUrlAndWaitForCompletion(FIRST_URL); |
| List<WebResourceRequest> requests = mTestHttpsWebViewClient.getInterceptedRequests(); |
| Assert.assertEquals(2, requests.size()); |
| |
| // Make sure the first request has low-entropy client hints. |
| WebResourceRequest recordedRequest = requests.get(0); |
| Assert.assertEquals(baseUrl + FIRST_URL, recordedRequest.getUrl().toString()); |
| Map<String, String> requestHeaders = recordedRequest.getRequestHeaders(); |
| Assert.assertEquals("\"myBrand\";v=\"1\"", |
| requestHeaders.get("sec-ch-ua")); |
| Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile")); |
| Assert.assertEquals("\"Android\"", |
| requestHeaders.get("sec-ch-ua-platform")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-platform-version")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-arch")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-full-version-list")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-bitness")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-model")); |
| Assert.assertNull(requestHeaders.get("sec-ch-ua-wow64")); |
| |
| // Verify all user-agent client hints on the second request. |
| recordedRequest = requests.get(1); |
| Assert.assertEquals(baseUrl + SECOND_URL, recordedRequest.getUrl().toString()); |
| requestHeaders = recordedRequest.getRequestHeaders(); |
| |
| Assert.assertEquals("\"myBrand\";v=\"1\"", |
| requestHeaders.get("sec-ch-ua")); |
| Assert.assertEquals("?1", requestHeaders.get("sec-ch-ua-mobile")); |
| Assert.assertNotNull(requestHeaders.get("sec-ch-ua-platform-version")); |
| Assert.assertNotNull(requestHeaders.get("sec-ch-ua-arch")); |
| Assert.assertNotNull(requestHeaders.get("sec-ch-ua-full-version-list")); |
| // The default bitness on HTTP header is empty string instead of 0. |
| Assert.assertEquals("\"\"", |
| requestHeaders.get("sec-ch-ua-bitness")); |
| Assert.assertNotNull(requestHeaders.get("sec-ch-ua-model")); |
| Assert.assertNotNull(requestHeaders.get("sec-ch-ua-wow64")); |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataBlankBrandVersion() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| try { |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| UserAgentMetadata.BrandVersion brandVersion = new UserAgentMetadata.BrandVersion |
| .Builder().setBrand("myBrand").build(); |
| UserAgentMetadata uaMetadata = new UserAgentMetadata.Builder() |
| .setBrandVersionList(Collections.singletonList(brandVersion)).build(); |
| WebSettingsCompat.setUserAgentMetadata(settings, uaMetadata); |
| Assert.fail("Should have thrown exception."); |
| } catch (IllegalStateException e) { |
| Assert.assertEquals("Brand name, major version and full version should not " |
| + "be null or blank.", e.getMessage()); |
| } |
| |
| try { |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| UserAgentMetadata.BrandVersion brandVersion = new UserAgentMetadata.BrandVersion |
| .Builder().setBrand("").build(); |
| UserAgentMetadata uaMetadata = new UserAgentMetadata.Builder() |
| .setBrandVersionList(Collections.singletonList(brandVersion)).build(); |
| WebSettingsCompat.setUserAgentMetadata(settings, uaMetadata); |
| Assert.fail("Should have thrown exception."); |
| } catch (IllegalArgumentException e) { |
| Assert.assertEquals("Brand should not be blank.", e.getMessage()); |
| } |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataBlankFullVersion() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| try { |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| UserAgentMetadata uaMetadata = new UserAgentMetadata.Builder() |
| .setFullVersion(" ").build(); |
| WebSettingsCompat.setUserAgentMetadata(settings, uaMetadata); |
| Assert.fail("Should have thrown exception."); |
| } catch (IllegalArgumentException e) { |
| Assert.assertEquals("Full version should not be blank.", e.getMessage()); |
| } |
| } |
| |
| @Test |
| public void testSetUserAgentMetadataBlankPlatform() throws Throwable { |
| WebkitUtils.checkFeature(WebViewFeature.USER_AGENT_METADATA); |
| |
| try { |
| WebSettings settings = mWebViewOnUiThread.getSettings(); |
| UserAgentMetadata uaMetadata = new UserAgentMetadata.Builder() |
| .setPlatform(" ").build(); |
| WebSettingsCompat.setUserAgentMetadata(settings, uaMetadata); |
| Assert.fail("Should have thrown exception."); |
| } catch (IllegalArgumentException e) { |
| Assert.assertEquals("Platform should not be blank.", e.getMessage()); |
| } |
| } |
| } |