16a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen/* 26a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Copyright (C) 2015 The Android Open Source Project 36a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * 46a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Licensed under the Apache License, Version 2.0 (the "License"); 56a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * you may not use this file except in compliance with the License. 66a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * You may obtain a copy of the License at 76a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * 86a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * http://www.apache.org/licenses/LICENSE-2.0 96a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * 106a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Unless required by applicable law or agreed to in writing, software 116a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * distributed under the License is distributed on an "AS IS" BASIS, 126a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 136a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * See the License for the specific language governing permissions and 146a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * limitations under the License. 156a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen */ 166a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 176a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenpackage com.android.statementservice.retriever; 186a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 196a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport org.json.JSONObject; 206a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 216a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.net.MalformedURLException; 226a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.net.URL; 236a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wenimport java.util.Locale; 246a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 256a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen/** 266a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Immutable value type that names a web asset. 276a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * 286a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * <p>A web asset can be named by its protocol, domain, and port using this JSON string: 296a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * { "namespace": "web", 306a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * "site": "[protocol]://[fully-qualified domain]{:[optional port]}" } 316a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * 326a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * <p>For example, a website hosted on a https server at www.test.com can be named using 336a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * { "namespace": "web", 346a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * "site": "https://www.test.com" } 356a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * 366a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * <p>The only protocol supported now are https and http. If the optional port is not specified, 376a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * the default for each protocol will be used (i.e. 80 for http and 443 for https). 386a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen */ 396a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen/* package private */ final class WebAsset extends AbstractAsset { 406a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 416a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen private static final String MISSING_FIELD_FORMAT_STRING = "Expected %s to be set."; 428c7d99c2b77acbcbbdcbf0dcab61a07767d5dd1bJoseph Wen private static final String SCHEME_HTTP = "http"; 436a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 446a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen private final URL mUrl; 456a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 466a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen private WebAsset(URL url) { 476a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen int port = url.getPort() != -1 ? url.getPort() : url.getDefaultPort(); 486a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen try { 496a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen mUrl = new URL(url.getProtocol().toLowerCase(), url.getHost().toLowerCase(), port, ""); 506a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } catch (MalformedURLException e) { 516a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen throw new AssertionError( 526a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen "Url should always be validated before calling the constructor."); 536a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 546a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 556a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 566a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public String getDomain() { 576a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return mUrl.getHost(); 586a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 596a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 606a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public String getPath() { 616a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return mUrl.getPath(); 626a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 636a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 646a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public String getScheme() { 656a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return mUrl.getProtocol(); 666a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 676a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 686a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public int getPort() { 696a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return mUrl.getPort(); 706a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 716a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 726a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen @Override 736a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public String toJson() { 746a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen AssetJsonWriter writer = new AssetJsonWriter(); 756a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 766a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen writer.writeFieldLower(Utils.NAMESPACE_FIELD, Utils.NAMESPACE_WEB); 776a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen writer.writeFieldLower(Utils.WEB_ASSET_FIELD_SITE, mUrl.toExternalForm()); 786a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 796a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return writer.closeAndGetString(); 806a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 816a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 826a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen @Override 836a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public String toString() { 846a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen StringBuilder asset = new StringBuilder(); 856a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen asset.append("WebAsset: "); 866a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen asset.append(toJson()); 876a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return asset.toString(); 886a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 896a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 906a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen @Override 916a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public boolean equals(Object o) { 926a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen if (!(o instanceof WebAsset)) { 936a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return false; 946a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 956a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 966a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return ((WebAsset) o).toJson().equals(toJson()); 976a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 986a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 996a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen @Override 1006a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public int hashCode() { 1016a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return toJson().hashCode(); 1026a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1036a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 1046a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen @Override 1056a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen public int lookupKey() { 1066a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return toJson().hashCode(); 1076a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1086a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 1098c7d99c2b77acbcbbdcbf0dcab61a07767d5dd1bJoseph Wen @Override 1108c7d99c2b77acbcbbdcbf0dcab61a07767d5dd1bJoseph Wen public boolean followInsecureInclude() { 1118c7d99c2b77acbcbbdcbf0dcab61a07767d5dd1bJoseph Wen // Only allow insecure include file if the asset scheme is http. 1128c7d99c2b77acbcbbdcbf0dcab61a07767d5dd1bJoseph Wen return SCHEME_HTTP.equals(getScheme()); 1138c7d99c2b77acbcbbdcbf0dcab61a07767d5dd1bJoseph Wen } 1148c7d99c2b77acbcbbdcbf0dcab61a07767d5dd1bJoseph Wen 1156a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen /** 1166a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * Checks that the input is a valid web asset. 1176a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * 1186a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen * @throws AssociationServiceException if the asset is not well formatted. 1196a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen */ 1206a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen protected static WebAsset create(JSONObject asset) 1216a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen throws AssociationServiceException { 1226a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen if (asset.optString(Utils.WEB_ASSET_FIELD_SITE).equals("")) { 1236a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen throw new AssociationServiceException(String.format(MISSING_FIELD_FORMAT_STRING, 1246a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen Utils.WEB_ASSET_FIELD_SITE)); 1256a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1266a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 1276a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen URL url; 1286a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen try { 1296a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen url = new URL(asset.optString(Utils.WEB_ASSET_FIELD_SITE)); 1306a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } catch (MalformedURLException e) { 1316a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen throw new AssociationServiceException("Url is not well formatted.", e); 1326a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1336a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 1346a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen String scheme = url.getProtocol().toLowerCase(Locale.US); 1356a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen if (!scheme.equals("https") && !scheme.equals("http")) { 1366a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen throw new AssociationServiceException("Expected scheme to be http or https."); 1376a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1386a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 1396a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen if (url.getUserInfo() != null) { 1406a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen throw new AssociationServiceException("The url should not contain user info."); 1416a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1426a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 1436a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen String path = url.getFile(); // This is url.getPath() + url.getQuery(). 1446a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen if (!path.equals("/") && !path.equals("")) { 1456a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen throw new AssociationServiceException( 1466a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen "Site should only have scheme, domain, and port."); 1476a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1486a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen 1496a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen return new WebAsset(url); 1506a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen } 1516a34bb2d6a6cbc7a70bdf0c53d238dc28e0b1d58Joseph Wen} 152