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 org.json.JSONObject;
20 
21 import java.net.MalformedURLException;
22 import java.net.URL;
23 import 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 
WebAsset(URL url)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 
getDomain()56     public String getDomain() {
57         return mUrl.getHost();
58     }
59 
getPath()60     public String getPath() {
61         return mUrl.getPath();
62     }
63 
getScheme()64     public String getScheme() {
65         return mUrl.getProtocol();
66     }
67 
getPort()68     public int getPort() {
69         return mUrl.getPort();
70     }
71 
72     @Override
toJson()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
toString()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
equals(Object o)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
hashCode()100     public int hashCode() {
101         return toJson().hashCode();
102     }
103 
104     @Override
lookupKey()105     public int lookupKey() {
106         return toJson().hashCode();
107     }
108 
109     @Override
followInsecureInclude()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      */
create(JSONObject asset)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