Chromium Code Reviews| 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 |