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