• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/renderer/manifest/manifest_parser.h"
6 
7 #include "base/json/json_reader.h"
8 #include "base/strings/nullable_string16.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_split.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "base/values.h"
14 #include "content/public/common/manifest.h"
15 #include "ui/gfx/geometry/size.h"
16 
17 namespace content {
18 
19 namespace {
20 
21 enum TrimType {
22   Trim,
23   NoTrim
24 };
25 
ParseString(const base::DictionaryValue & dictionary,const std::string & key,TrimType trim)26 base::NullableString16 ParseString(const base::DictionaryValue& dictionary,
27                                    const std::string& key,
28                                    TrimType trim) {
29   if (!dictionary.HasKey(key))
30     return base::NullableString16();
31 
32   base::string16 value;
33   if (!dictionary.GetString(key, &value)) {
34     // TODO(mlamouri): provide a custom message to the developer console about
35     // the property being incorrectly set.
36     return base::NullableString16();
37   }
38 
39   if (trim == Trim)
40     base::TrimWhitespace(value, base::TRIM_ALL, &value);
41   return base::NullableString16(value, false);
42 }
43 
44 // Helper function to parse URLs present on a given |dictionary| in a given
45 // field identified by its |key|. The URL is first parsed as a string then
46 // resolved using |base_url|.
47 // Returns a GURL. If the parsing failed, the GURL will not be valid.
ParseURL(const base::DictionaryValue & dictionary,const std::string & key,const GURL & base_url)48 GURL ParseURL(const base::DictionaryValue& dictionary,
49               const std::string& key,
50               const GURL& base_url) {
51   base::NullableString16 url_str = ParseString(dictionary, key, NoTrim);
52   if (url_str.is_null())
53     return GURL();
54 
55   return base_url.Resolve(url_str.string());
56 }
57 
58 // Parses the 'name' field of the manifest, as defined in:
59 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-name-member
60 // Returns the parsed string if any, a null string if the parsing failed.
ParseName(const base::DictionaryValue & dictionary)61 base::NullableString16 ParseName(const base::DictionaryValue& dictionary)  {
62   return ParseString(dictionary, "name", Trim);
63 }
64 
65 // Parses the 'short_name' field of the manifest, as defined in:
66 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-short-name-member
67 // Returns the parsed string if any, a null string if the parsing failed.
ParseShortName(const base::DictionaryValue & dictionary)68 base::NullableString16 ParseShortName(
69     const base::DictionaryValue& dictionary)  {
70   return ParseString(dictionary, "short_name", Trim);
71 }
72 
73 // Parses the 'start_url' field of the manifest, as defined in:
74 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-start_url-member
75 // Returns the parsed GURL if any, an empty GURL if the parsing failed.
ParseStartURL(const base::DictionaryValue & dictionary,const GURL & manifest_url,const GURL & document_url)76 GURL ParseStartURL(const base::DictionaryValue& dictionary,
77                    const GURL& manifest_url,
78                    const GURL& document_url) {
79   GURL start_url = ParseURL(dictionary, "start_url", manifest_url);
80   if (!start_url.is_valid())
81     return GURL();
82 
83   if (start_url.GetOrigin() != document_url.GetOrigin()) {
84     // TODO(mlamouri): provide a custom message to the developer console.
85     return GURL();
86   }
87 
88   return start_url;
89 }
90 
91 // Parses the 'display' field of the manifest, as defined in:
92 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-display-member
93 // Returns the parsed DisplayMode if any, DISPLAY_MODE_UNSPECIFIED if the
94 // parsing failed.
ParseDisplay(const base::DictionaryValue & dictionary)95 Manifest::DisplayMode ParseDisplay(const base::DictionaryValue& dictionary) {
96   base::NullableString16 display = ParseString(dictionary, "display", Trim);
97 
98   if (display.is_null())
99     return Manifest::DISPLAY_MODE_UNSPECIFIED;
100 
101   if (LowerCaseEqualsASCII(display.string(), "fullscreen"))
102     return Manifest::DISPLAY_MODE_FULLSCREEN;
103   else if (LowerCaseEqualsASCII(display.string(), "standalone"))
104     return Manifest::DISPLAY_MODE_STANDALONE;
105   else if (LowerCaseEqualsASCII(display.string(), "minimal-ui"))
106     return Manifest::DISPLAY_MODE_MINIMAL_UI;
107   else if (LowerCaseEqualsASCII(display.string(), "browser"))
108     return Manifest::DISPLAY_MODE_BROWSER;
109   else
110     return Manifest::DISPLAY_MODE_UNSPECIFIED;
111 }
112 
113 // Parses the 'orientation' field of the manifest, as defined in:
114 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-orientation-member
115 // Returns the parsed WebScreenOrientationLockType if any,
116 // WebScreenOrientationLockDefault if the parsing failed.
ParseOrientation(const base::DictionaryValue & dictionary)117 blink::WebScreenOrientationLockType ParseOrientation(
118     const base::DictionaryValue& dictionary) {
119   base::NullableString16 orientation =
120       ParseString(dictionary, "orientation", Trim);
121 
122   if (orientation.is_null())
123     return blink::WebScreenOrientationLockDefault;
124 
125   if (LowerCaseEqualsASCII(orientation.string(), "any"))
126     return blink::WebScreenOrientationLockAny;
127   else if (LowerCaseEqualsASCII(orientation.string(), "natural"))
128     return blink::WebScreenOrientationLockNatural;
129   else if (LowerCaseEqualsASCII(orientation.string(), "landscape"))
130     return blink::WebScreenOrientationLockLandscape;
131   else if (LowerCaseEqualsASCII(orientation.string(), "landscape-primary"))
132     return blink::WebScreenOrientationLockLandscapePrimary;
133   else if (LowerCaseEqualsASCII(orientation.string(), "landscape-secondary"))
134     return blink::WebScreenOrientationLockLandscapeSecondary;
135   else if (LowerCaseEqualsASCII(orientation.string(), "portrait"))
136     return blink::WebScreenOrientationLockPortrait;
137   else if (LowerCaseEqualsASCII(orientation.string(), "portrait-primary"))
138     return blink::WebScreenOrientationLockPortraitPrimary;
139   else if (LowerCaseEqualsASCII(orientation.string(), "portrait-secondary"))
140     return blink::WebScreenOrientationLockPortraitSecondary;
141   else
142     return blink::WebScreenOrientationLockDefault;
143 }
144 
145 // Parses the 'src' field of an icon, as defined in:
146 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-src-member-of-an-icon
147 // Returns the parsed GURL if any, an empty GURL if the parsing failed.
ParseIconSrc(const base::DictionaryValue & icon,const GURL & manifest_url)148 GURL ParseIconSrc(const base::DictionaryValue& icon,
149                   const GURL& manifest_url) {
150   return ParseURL(icon, "src", manifest_url);
151 }
152 
153 // Parses the 'type' field of an icon, as defined in:
154 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-type-member-of-an-icon
155 // Returns the parsed string if any, a null string if the parsing failed.
ParseIconType(const base::DictionaryValue & icon)156 base::NullableString16 ParseIconType(const base::DictionaryValue& icon) {
157     return ParseString(icon, "type", Trim);
158 }
159 
160 // Parses the 'density' field of an icon, as defined in:
161 // http://w3c.github.io/manifest/#dfn-steps-for-processing-a-density-member-of-an-icon
162 // Returns the parsed double if any, Manifest::Icon::kDefaultDensity if the
163 // parsing failed.
ParseIconDensity(const base::DictionaryValue & icon)164 double ParseIconDensity(const base::DictionaryValue& icon) {
165   double density;
166   if (!icon.GetDouble("density", &density) || density <= 0)
167     return Manifest::Icon::kDefaultDensity;
168   return density;
169 }
170 
171 // Helper function that returns whether the given |str| is a valid width or
172 // height value for an icon sizes per:
173 // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes
IsValidIconWidthOrHeight(const std::string & str)174 bool IsValidIconWidthOrHeight(const std::string& str) {
175   if (str.empty() || str[0] == '0')
176     return false;
177   for (size_t i = 0; i < str.size(); ++i)
178     if (!IsAsciiDigit(str[i]))
179       return false;
180   return true;
181 }
182 
183 // Parses the 'sizes' attribute of an icon as described in the HTML spec:
184 // https://html.spec.whatwg.org/multipage/semantics.html#attr-link-sizes
185 // Return a vector of gfx::Size that contains the valid sizes found. "Any" is
186 // represented by gfx::Size(0, 0).
187 // TODO(mlamouri): this is implemented as a separate function because it should
188 // be refactored with the other icon sizes parsing implementations, see
189 // http://crbug.com/416477
ParseIconSizesHTML(const base::string16 & sizes_str16)190 std::vector<gfx::Size> ParseIconSizesHTML(const base::string16& sizes_str16) {
191   if (!base::IsStringASCII(sizes_str16))
192     return std::vector<gfx::Size>();
193 
194   std::vector<gfx::Size> sizes;
195   std::string sizes_str =
196       base::StringToLowerASCII(base::UTF16ToUTF8(sizes_str16));
197   std::vector<std::string> sizes_str_list;
198   base::SplitStringAlongWhitespace(sizes_str, &sizes_str_list);
199 
200   for (size_t i = 0; i < sizes_str_list.size(); ++i) {
201     std::string& size_str = sizes_str_list[i];
202     if (size_str == "any") {
203       sizes.push_back(gfx::Size(0, 0));
204       continue;
205     }
206 
207     // It is expected that [0] => width and [1] => height after the split.
208     std::vector<std::string> size_list;
209     base::SplitStringDontTrim(size_str, L'x', &size_list);
210     if (size_list.size() != 2)
211       continue;
212     if (!IsValidIconWidthOrHeight(size_list[0]) ||
213         !IsValidIconWidthOrHeight(size_list[1])) {
214       continue;
215     }
216 
217     int width, height;
218     if (!base::StringToInt(size_list[0], &width) ||
219         !base::StringToInt(size_list[1], &height)) {
220       continue;
221     }
222 
223     sizes.push_back(gfx::Size(width, height));
224   }
225 
226   return sizes;
227 }
228 
229 // Parses the 'sizes' field of an icon, as defined in:
230 // http://w3c.github.io/manifest/#dfn-steps-for-processing-a-sizes-member-of-an-icon
231 // Returns a vector of gfx::Size with the successfully parsed sizes, if any. An
232 // empty vector if the field was not present or empty. "Any" is represented by
233 // gfx::Size(0, 0).
ParseIconSizes(const base::DictionaryValue & icon)234 std::vector<gfx::Size> ParseIconSizes(const base::DictionaryValue& icon) {
235   base::NullableString16 sizes_str = ParseString(icon, "sizes", NoTrim);
236 
237   return sizes_str.is_null() ? std::vector<gfx::Size>()
238                              : ParseIconSizesHTML(sizes_str.string());
239 }
240 
241 // Parses the 'icons' field of a Manifest, as defined in:
242 // http://w3c.github.io/manifest/#dfn-steps-for-processing-the-icons-member
243 // Returns a vector of Manifest::Icon with the successfully parsed icons, if
244 // any. An empty vector if the field was not present or empty.
ParseIcons(const base::DictionaryValue & dictionary,const GURL & manifest_url)245 std::vector<Manifest::Icon> ParseIcons(const base::DictionaryValue& dictionary,
246                                        const GURL& manifest_url) {
247   std::vector<Manifest::Icon> icons;
248   if (!dictionary.HasKey("icons"))
249     return icons;
250 
251   const base::ListValue* icons_list = 0;
252   if (!dictionary.GetList("icons", &icons_list)) {
253     // TODO(mlamouri): provide a custom message to the developer console about
254     // the property being incorrectly set.
255     return icons;
256   }
257 
258   for (size_t i = 0; i < icons_list->GetSize(); ++i) {
259     const base::DictionaryValue* icon_dictionary = 0;
260     if (!icons_list->GetDictionary(i, &icon_dictionary))
261       continue;
262 
263     Manifest::Icon icon;
264     icon.src = ParseIconSrc(*icon_dictionary, manifest_url);
265     // An icon MUST have a valid src. If it does not, it MUST be ignored.
266     if (!icon.src.is_valid())
267       continue;
268     icon.type = ParseIconType(*icon_dictionary);
269     icon.density = ParseIconDensity(*icon_dictionary);
270     icon.sizes = ParseIconSizes(*icon_dictionary);
271 
272     icons.push_back(icon);
273   }
274 
275   return icons;
276 }
277 
278 } // anonymous namespace
279 
Parse(const base::StringPiece & json,const GURL & manifest_url,const GURL & document_url)280 Manifest ManifestParser::Parse(const base::StringPiece& json,
281                                const GURL& manifest_url,
282                                const GURL& document_url) {
283   scoped_ptr<base::Value> value(base::JSONReader::Read(json));
284   if (!value) {
285     // TODO(mlamouri): get the JSON parsing error and report it to the developer
286     // console.
287     return Manifest();
288   }
289 
290   if (value->GetType() != base::Value::TYPE_DICTIONARY) {
291     // TODO(mlamouri): provide a custom message to the developer console.
292     return Manifest();
293   }
294 
295   base::DictionaryValue* dictionary = 0;
296   value->GetAsDictionary(&dictionary);
297   if (!dictionary) {
298     // TODO(mlamouri): provide a custom message to the developer console.
299     return Manifest();
300   }
301 
302   Manifest manifest;
303 
304   manifest.name = ParseName(*dictionary);
305   manifest.short_name = ParseShortName(*dictionary);
306   manifest.start_url = ParseStartURL(*dictionary, manifest_url, document_url);
307   manifest.display = ParseDisplay(*dictionary);
308   manifest.orientation = ParseOrientation(*dictionary);
309   manifest.icons = ParseIcons(*dictionary, manifest_url);
310 
311   return manifest;
312 }
313 
314 } // namespace content
315