OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2010 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 "chrome/common/web_apps.h" | |
6 | |
7 #include <string> | |
8 #include <vector> | |
9 | |
10 #include "app/l10n_util.h" | |
11 #include "app/resource_bundle.h" | |
12 #include "base/json/json_reader.h" | |
13 #include "base/string16.h" | |
14 #include "base/string_number_conversions.h" | |
15 #include "base/string_split.h" | |
16 #include "base/utf_string_conversions.h" | |
17 #include "base/values.h" | |
18 #include "chrome/common/json_schema_validator.h" | |
19 #include "gfx/size.h" | |
20 #include "googleurl/src/gurl.h" | |
21 #include "grit/common_resources.h" | |
22 #include "grit/generated_resources.h" | |
23 #include "third_party/WebKit/WebKit/chromium/public/WebDocument.h" | |
24 #include "third_party/WebKit/WebKit/chromium/public/WebElement.h" | |
25 #include "third_party/WebKit/WebKit/chromium/public/WebFrame.h" | |
26 #include "third_party/WebKit/WebKit/chromium/public/WebNode.h" | |
27 #include "third_party/WebKit/WebKit/chromium/public/WebNodeList.h" | |
28 #include "third_party/WebKit/WebKit/chromium/public/WebString.h" | |
29 #include "third_party/WebKit/WebKit/chromium/public/WebURL.h" | |
30 #include "webkit/glue/dom_operations.h" | |
31 | |
32 using WebKit::WebDocument; | |
33 using WebKit::WebElement; | |
34 using WebKit::WebFrame; | |
35 using WebKit::WebNode; | |
36 using WebKit::WebNodeList; | |
37 using WebKit::WebString; | |
38 | |
39 namespace { | |
40 | |
41 // Sizes a single size (the width or height) from a 'sizes' attribute. A size | |
42 // matches must match the following regex: [1-9][0-9]*. | |
43 static int ParseSingleIconSize(const string16& text) { | |
44 // Size must not start with 0, and be between 0 and 9. | |
45 if (text.empty() || !(text[0] >= L'1' && text[0] <= L'9')) | |
46 return 0; | |
Erik does not do reviews
2010/11/15 19:45:36
the header indicates a size < 0 if it's invalid
Aaron Boodman
2010/11/15 19:52:34
I forgot to mention this in the review, but ParseS
Aaron Boodman
2010/11/16 03:44:45
Not my code, but fixed.
| |
47 // Make sure all chars are from 0-9. | |
Erik does not do reviews
2010/11/15 19:45:36
nit: newline above comment
Aaron Boodman
2010/11/16 03:44:45
Done.
| |
48 for (size_t i = 1; i < text.length(); ++i) { | |
49 if (!(text[i] >= L'0' && text[i] <= L'9')) | |
50 return 0; | |
51 } | |
52 int output; | |
53 if (!base::StringToInt(text, &output)) | |
54 return 0; | |
55 return output; | |
Erik does not do reviews
2010/11/15 19:45:36
do we want a min/max value for icon sizes?
Aaron Boodman
2010/11/16 03:44:45
<shrug> -- this code is only used by the create ap
| |
56 } | |
57 | |
58 void AddInstallIcon(const WebElement& link, | |
59 std::vector<WebApplicationInfo::IconInfo>* icons) { | |
60 WebString href = link.getAttribute("href"); | |
61 if (href.isNull() || href.isEmpty()) | |
62 return; | |
63 | |
64 // Get complete url. | |
65 GURL url = link.document().completeURL(href); | |
66 if (!url.is_valid()) | |
67 return; | |
68 | |
69 if (!link.hasAttribute("sizes")) | |
70 return; | |
71 | |
72 bool is_any = false; | |
73 std::vector<gfx::Size> icon_sizes; | |
74 if (!ParseIconSizes(link.getAttribute("sizes"), &icon_sizes, &is_any) || | |
75 is_any || | |
76 icon_sizes.size() != 1) { | |
Erik does not do reviews
2010/11/15 19:45:36
so this means we're explicitly not allowing things
Aaron Boodman
2010/11/16 03:44:45
Also only used by create application shortcuts fea
| |
77 return; | |
78 } | |
79 WebApplicationInfo::IconInfo icon_info; | |
80 icon_info.width = icon_sizes[0].width(); | |
81 icon_info.height = icon_sizes[0].height(); | |
82 icon_info.url = url; | |
83 icons->push_back(icon_info); | |
84 } | |
85 | |
86 } | |
87 | |
88 const char WebApplicationInfo::kInvalidDefinitionURL[] = | |
89 "Invalid application definition URL. Must be a valid relative URL or " | |
90 "an absolute URL with the same origin as the document."; | |
91 const char WebApplicationInfo::kInvalidLaunchURL[] = | |
92 "Invalid value for property 'launch_url'. Must be a valid relative URL or " | |
93 "an absolute URL with the same origin as the application definition."; | |
94 const char WebApplicationInfo::kInvalidURL[] = | |
95 "Invalid value for property 'urls[*]'. Must be a valid relative URL or " | |
96 "an absolute URL with the same origin as the application definition."; | |
97 const char WebApplicationInfo::kInvalidIconURL[] = | |
98 "Invalid value for property 'icons.*'. Must be a valid relative URL or " | |
99 "an absolute URL with the same origin as the application definition."; | |
100 | |
101 WebApplicationInfo::WebApplicationInfo() { | |
102 } | |
103 | |
104 WebApplicationInfo::~WebApplicationInfo() { | |
105 } | |
106 | |
107 gfx::Size ParseIconSize(const string16& text) { | |
108 std::vector<string16> sizes; | |
109 base::SplitStringDontTrim(text, L'x', &sizes); | |
110 if (sizes.size() != 2) | |
111 return gfx::Size(); | |
112 | |
113 return gfx::Size(ParseSingleIconSize(sizes[0]), | |
114 ParseSingleIconSize(sizes[1])); | |
115 } | |
116 | |
117 bool ParseIconSizes(const string16& text, | |
118 std::vector<gfx::Size>* sizes, | |
119 bool* is_any) { | |
120 *is_any = false; | |
121 std::vector<string16> size_strings; | |
122 SplitStringAlongWhitespace(text, &size_strings); | |
123 for (size_t i = 0; i < size_strings.size(); ++i) { | |
124 if (EqualsASCII(size_strings[i], "any")) { | |
125 *is_any = true; | |
126 } else { | |
127 gfx::Size size = ParseIconSize(size_strings[i]); | |
128 if (size.width() <= 0 || size.height() <= 0) | |
129 return false; // Bogus size. | |
130 sizes->push_back(size); | |
131 } | |
132 } | |
133 if (*is_any && !sizes->empty()) { | |
134 // If is_any is true, it must occur by itself. | |
Erik does not do reviews
2010/11/15 19:45:36
Without displaying error messages to the developer
Aaron Boodman
2010/11/16 03:44:45
This is only used by the create application shortc
| |
135 return false; | |
136 } | |
137 return (*is_any || !sizes->empty()); | |
138 } | |
139 | |
140 bool ParseWebAppFromWebDocument(WebFrame* frame, | |
141 WebApplicationInfo* app_info, | |
142 string16* error) { | |
143 WebDocument document = frame->document(); | |
144 if (document.isNull()) | |
145 return true; | |
146 | |
147 WebElement head = document.head(); | |
148 if (head.isNull()) | |
149 return true; | |
150 | |
151 GURL frame_url = frame->url(); | |
152 WebNodeList children = head.childNodes(); | |
153 for (unsigned i = 0; i < children.length(); ++i) { | |
154 WebNode child = children.item(i); | |
155 if (!child.isElementNode()) | |
156 continue; | |
157 WebElement elem = child.to<WebElement>(); | |
158 | |
159 if (elem.hasTagName("link")) { | |
160 std::string rel = elem.getAttribute("rel").utf8(); | |
161 // "rel" attribute may use either "icon" or "shortcut icon". | |
162 // see also | |
163 // <http://en.wikipedia.org/wiki/Favicon> | |
164 // <http://dev.w3.org/html5/spec/Overview.html#rel-icon> | |
165 if (LowerCaseEqualsASCII(rel, "icon") || | |
166 LowerCaseEqualsASCII(rel, "shortcut icon")) { | |
167 AddInstallIcon(elem, &app_info->icons); | |
168 } else if (LowerCaseEqualsASCII(rel, "chrome-application-definition")) { | |
169 std::string definition_url_string(elem.getAttribute("href").utf8()); | |
170 GURL definition_url; | |
171 if (!(definition_url = | |
172 frame_url.Resolve(definition_url_string)).is_valid() || | |
173 definition_url.GetOrigin() != frame_url.GetOrigin()) { | |
174 *error = UTF8ToUTF16(WebApplicationInfo::kInvalidDefinitionURL); | |
175 return false; | |
176 } | |
177 | |
178 // If there is a definition file, all attributes come from it. | |
179 *app_info = WebApplicationInfo(); | |
180 app_info->manifest_url = definition_url; | |
181 return true; | |
182 } | |
183 } else if (elem.hasTagName("meta") && elem.hasAttribute("name")) { | |
184 std::string name = elem.getAttribute("name").utf8(); | |
185 WebString content = elem.getAttribute("content"); | |
186 if (name == "application-name") { | |
187 app_info->title = content; | |
188 } else if (name == "description") { | |
189 app_info->description = content; | |
190 } else if (name == "application-url") { | |
191 std::string url = content.utf8(); | |
192 app_info->app_url = frame_url.is_valid() ? | |
193 frame_url.Resolve(url) : GURL(url); | |
194 if (!app_info->app_url.is_valid()) | |
195 app_info->app_url = GURL(); | |
196 } | |
197 } | |
198 } | |
199 | |
200 return true; | |
201 } | |
202 | |
203 bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition, | |
204 WebApplicationInfo* web_app, | |
205 string16* error) { | |
206 CHECK(web_app->manifest_url.is_valid()); | |
207 | |
208 int error_code = 0; | |
209 std::string error_message; | |
210 scoped_ptr<Value> schema( | |
211 base::JSONReader::ReadAndReturnError( | |
212 ResourceBundle::GetSharedInstance().GetRawDataResource( | |
213 IDR_WEB_APP_SCHEMA).as_string(), | |
214 false, // disallow trailing comma | |
215 &error_code, | |
216 &error_message)); | |
217 CHECK(schema.get()) | |
218 << "Error parsing JSON schema: " << error_code << ": " << error_message; | |
219 CHECK(schema->IsType(Value::TYPE_DICTIONARY)) | |
220 << "schema root must be dictionary."; | |
221 | |
222 JSONSchemaValidator validator(static_cast<DictionaryValue*>(schema.get())); | |
223 | |
224 // We allow extra properties in the schema for easy compat with other systems, | |
225 // and for forward compat with ourselves. | |
226 validator.set_default_allow_additional_properties(true); | |
227 | |
228 if (!validator.Validate(const_cast<DictionaryValue*>(&definition))) { | |
229 *error = UTF8ToUTF16( | |
230 validator.errors()[0].path + ": " + validator.errors()[0].message); | |
231 return false; | |
232 } | |
233 | |
234 // Parse launch URL. It must be a valid URL in the same origin as the | |
235 // manifest. | |
236 std::string app_url_string; | |
237 GURL app_url; | |
238 CHECK(definition.GetString("launch_url", &app_url_string)); | |
239 if (!(app_url = web_app->manifest_url.Resolve(app_url_string)).is_valid() || | |
240 app_url.GetOrigin() != web_app->manifest_url.GetOrigin()) { | |
241 *error = UTF8ToUTF16(WebApplicationInfo::kInvalidLaunchURL); | |
242 return false; | |
243 } | |
244 | |
245 // Parse out the privileges if present. | |
246 std::vector<std::string> privileges; | |
247 ListValue* privileges_value = NULL; | |
248 if (definition.GetList("privileges", &privileges_value)) { | |
249 for (size_t i = 0; i < privileges_value->GetSize(); ++i) { | |
250 std::string privilege; | |
251 CHECK(privileges_value->GetString(i, &privilege)); | |
Erik does not do reviews
2010/11/15 19:45:36
isn't this covered by the schema validation?
Aaron Boodman
2010/11/16 03:44:45
The CHECK is just a quick assertion. I prefer that
| |
252 privileges.push_back(privilege); | |
253 } | |
254 } | |
255 | |
256 // Parse out the URLs if present. | |
257 std::vector<GURL> urls; | |
258 ListValue* urls_value = NULL; | |
259 if (definition.GetList("urls", &urls_value)) { | |
260 for (size_t i = 0; i < urls_value->GetSize(); ++i) { | |
261 std::string url_string; | |
262 GURL url; | |
263 CHECK(urls_value->GetString(i, &url_string)); | |
Erik does not do reviews
2010/11/15 19:45:36
again, is this redundant?
| |
264 if (!(url = web_app->manifest_url.Resolve(url_string)).is_valid() || | |
265 url.GetOrigin() != web_app->manifest_url.GetOrigin()) { | |
266 *error = UTF8ToUTF16( | |
267 JSONSchemaValidator::FormatErrorMessage( | |
268 WebApplicationInfo::kInvalidURL, base::UintToString(i))); | |
269 return false; | |
270 } | |
271 urls.push_back(url); | |
272 } | |
273 } | |
274 | |
275 // Parse out the icons if present. | |
276 std::vector<WebApplicationInfo::IconInfo> icons; | |
277 DictionaryValue* icons_value = NULL; | |
278 if (definition.GetDictionary("icons", &icons_value)) { | |
279 for (DictionaryValue::key_iterator iter = icons_value->begin_keys(); | |
280 iter != icons_value->end_keys(); ++iter) { | |
281 // Ignore unknown properties. Better for forward compat. | |
282 int size = 0; | |
283 if (!base::StringToInt(*iter, &size) || size < 0 || size > 128) | |
Erik does not do reviews
2010/11/15 19:45:36
128 seems too small as a max. 512 is pretty stand
Aaron Boodman
2010/11/16 03:44:45
The extension system doesn't currently support any
Erik does not do reviews
2010/11/16 23:43:02
I forgot that we had that hard-coded. I'll file a
| |
284 continue; | |
285 | |
286 std::string icon_url_string; | |
287 GURL icon_url; | |
288 if (!icons_value->GetString(*iter, &icon_url_string) || | |
289 !(icon_url = web_app->manifest_url.Resolve( | |
290 icon_url_string)).is_valid()) { | |
291 *error = UTF8ToUTF16( | |
292 JSONSchemaValidator::FormatErrorMessage( | |
293 WebApplicationInfo::kInvalidIconURL, base::IntToString(size))); | |
294 return false; | |
295 } | |
296 | |
297 WebApplicationInfo::IconInfo icon; | |
298 icon.url = icon_url; | |
299 icon.width = size; | |
300 icon.height = size; | |
301 | |
302 icons.push_back(icon); | |
303 } | |
304 } | |
305 | |
306 CHECK(definition.GetString("name", &web_app->title)); | |
307 definition.GetString("description", &web_app->description); | |
308 definition.GetString("launch_container", &web_app->launch_container); | |
309 web_app->app_url = app_url; | |
310 web_app->urls = urls; | |
311 web_app->privileges = privileges; | |
312 web_app->icons = icons; | |
313 | |
314 return true; | |
315 | |
316 } | |
OLD | NEW |