1/*
2 * Copyright (C) 2015 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
17package com.android.statementservice.retriever;
18
19import org.json.JSONObject;
20
21import java.net.MalformedURLException;
22import java.net.URL;
23import java.util.Locale;
24
25/**
26 * Immutable value type that names a web asset.
27 *
28 * <p>A web asset can be named by its protocol, domain, and port using this JSON string:
29 *     { "namespace": "web",
30 *       "site": "[protocol]://[fully-qualified domain]{:[optional port]}" }
31 *
32 * <p>For example, a website hosted on a https server at www.test.com can be named using
33 *     { "namespace": "web",
34 *       "site": "https://www.test.com" }
35 *
36 * <p>The only protocol supported now are https and http. If the optional port is not specified,
37 * the default for each protocol will be used (i.e. 80 for http and 443 for https).
38 */
39/* package private */ final class WebAsset extends AbstractAsset {
40
41    private static final String MISSING_FIELD_FORMAT_STRING = "Expected %s to be set.";
42    private static final String SCHEME_HTTP = "http";
43
44    private final URL mUrl;
45
46    private WebAsset(URL url) {
47        int port = url.getPort() != -1 ? url.getPort() : url.getDefaultPort();
48        try {
49            mUrl = new URL(url.getProtocol().toLowerCase(), url.getHost().toLowerCase(), port, "");
50        } catch (MalformedURLException e) {
51            throw new AssertionError(
52                    "Url should always be validated before calling the constructor.");
53        }
54    }
55
56    public String getDomain() {
57        return mUrl.getHost();
58    }
59
60    public String getPath() {
61        return mUrl.getPath();
62    }
63
64    public String getScheme() {
65        return mUrl.getProtocol();
66    }
67
68    public int getPort() {
69        return mUrl.getPort();
70    }
71
72    @Override
73    public String toJson() {
74        AssetJsonWriter writer = new AssetJsonWriter();
75
76        writer.writeFieldLower(Utils.NAMESPACE_FIELD, Utils.NAMESPACE_WEB);
77        writer.writeFieldLower(Utils.WEB_ASSET_FIELD_SITE, mUrl.toExternalForm());
78
79        return writer.closeAndGetString();
80    }
81
82    @Override
83    public String toString() {
84        StringBuilder asset = new StringBuilder();
85        asset.append("WebAsset: ");
86        asset.append(toJson());
87        return asset.toString();
88    }
89
90    @Override
91    public boolean equals(Object o) {
92        if (!(o instanceof WebAsset)) {
93            return false;
94        }
95
96        return ((WebAsset) o).toJson().equals(toJson());
97    }
98
99    @Override
100    public int hashCode() {
101        return toJson().hashCode();
102    }
103
104    @Override
105    public int lookupKey() {
106        return toJson().hashCode();
107    }
108
109    @Override
110    public boolean followInsecureInclude() {
111        // Only allow insecure include file if the asset scheme is http.
112        return SCHEME_HTTP.equals(getScheme());
113    }
114
115    /**
116     * Checks that the input is a valid web asset.
117     *
118     * @throws AssociationServiceException if the asset is not well formatted.
119     */
120    protected static WebAsset create(JSONObject asset)
121            throws AssociationServiceException {
122        if (asset.optString(Utils.WEB_ASSET_FIELD_SITE).equals("")) {
123            throw new AssociationServiceException(String.format(MISSING_FIELD_FORMAT_STRING,
124                    Utils.WEB_ASSET_FIELD_SITE));
125        }
126
127        URL url;
128        try {
129            url = new URL(asset.optString(Utils.WEB_ASSET_FIELD_SITE));
130        } catch (MalformedURLException e) {
131            throw new AssociationServiceException("Url is not well formatted.", e);
132        }
133
134        String scheme = url.getProtocol().toLowerCase(Locale.US);
135        if (!scheme.equals("https") && !scheme.equals("http")) {
136            throw new AssociationServiceException("Expected scheme to be http or https.");
137        }
138
139        if (url.getUserInfo() != null) {
140            throw new AssociationServiceException("The url should not contain user info.");
141        }
142
143        String path = url.getFile(); // This is url.getPath() + url.getQuery().
144        if (!path.equals("/") && !path.equals("")) {
145            throw new AssociationServiceException(
146                    "Site should only have scheme, domain, and port.");
147        }
148
149        return new WebAsset(url);
150    }
151}
152