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