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 -1; |
| 47 |
| 48 // Make sure all chars are from 0-9. |
| 49 for (size_t i = 1; i < text.length(); ++i) { |
| 50 if (!(text[i] >= L'0' && text[i] <= L'9')) |
| 51 return -1; |
| 52 } |
| 53 int output; |
| 54 if (!base::StringToInt(text, &output)) |
| 55 return -1; |
| 56 return output; |
| 57 } |
| 58 |
| 59 void AddInstallIcon(const WebElement& link, |
| 60 std::vector<WebApplicationInfo::IconInfo>* icons) { |
| 61 WebString href = link.getAttribute("href"); |
| 62 if (href.isNull() || href.isEmpty()) |
| 63 return; |
| 64 |
| 65 // Get complete url. |
| 66 GURL url = link.document().completeURL(href); |
| 67 if (!url.is_valid()) |
| 68 return; |
| 69 |
| 70 if (!link.hasAttribute("sizes")) |
| 71 return; |
| 72 |
| 73 bool is_any = false; |
| 74 std::vector<gfx::Size> icon_sizes; |
| 75 if (!ParseIconSizes(link.getAttribute("sizes"), &icon_sizes, &is_any) || |
| 76 is_any || |
| 77 icon_sizes.size() != 1) { |
| 78 return; |
| 79 } |
| 80 WebApplicationInfo::IconInfo icon_info; |
| 81 icon_info.width = icon_sizes[0].width(); |
| 82 icon_info.height = icon_sizes[0].height(); |
| 83 icon_info.url = url; |
| 84 icons->push_back(icon_info); |
| 85 } |
| 86 |
| 87 } |
| 88 |
| 89 const char WebApplicationInfo::kInvalidDefinitionURL[] = |
| 90 "Invalid application definition URL. Must be a valid relative URL or " |
| 91 "an absolute URL with the same origin as the document."; |
| 92 const char WebApplicationInfo::kInvalidLaunchURL[] = |
| 93 "Invalid value for property 'launch_url'. Must be a valid relative URL or " |
| 94 "an absolute URL with the same origin as the application definition."; |
| 95 const char WebApplicationInfo::kInvalidURL[] = |
| 96 "Invalid value for property 'urls[*]'. Must be a valid relative URL or " |
| 97 "an absolute URL with the same origin as the application definition."; |
| 98 const char WebApplicationInfo::kInvalidIconURL[] = |
| 99 "Invalid value for property 'icons.*'. Must be a valid relative URL or " |
| 100 "an absolute URL with the same origin as the application definition."; |
| 101 |
| 102 WebApplicationInfo::WebApplicationInfo() { |
| 103 } |
| 104 |
| 105 WebApplicationInfo::~WebApplicationInfo() { |
| 106 } |
| 107 |
| 108 gfx::Size ParseIconSize(const string16& text) { |
| 109 std::vector<string16> sizes; |
| 110 base::SplitStringDontTrim(text, L'x', &sizes); |
| 111 if (sizes.size() != 2) |
| 112 return gfx::Size(); |
| 113 |
| 114 return gfx::Size(ParseSingleIconSize(sizes[0]), |
| 115 ParseSingleIconSize(sizes[1])); |
| 116 } |
| 117 |
| 118 bool ParseIconSizes(const string16& text, |
| 119 std::vector<gfx::Size>* sizes, |
| 120 bool* is_any) { |
| 121 *is_any = false; |
| 122 std::vector<string16> size_strings; |
| 123 SplitStringAlongWhitespace(text, &size_strings); |
| 124 for (size_t i = 0; i < size_strings.size(); ++i) { |
| 125 if (EqualsASCII(size_strings[i], "any")) { |
| 126 *is_any = true; |
| 127 } else { |
| 128 gfx::Size size = ParseIconSize(size_strings[i]); |
| 129 if (size.width() <= 0 || size.height() <= 0) |
| 130 return false; // Bogus size. |
| 131 sizes->push_back(size); |
| 132 } |
| 133 } |
| 134 if (*is_any && !sizes->empty()) { |
| 135 // If is_any is true, it must occur by itself. |
| 136 return false; |
| 137 } |
| 138 return (*is_any || !sizes->empty()); |
| 139 } |
| 140 |
| 141 bool ParseWebAppFromWebDocument(WebFrame* frame, |
| 142 WebApplicationInfo* app_info, |
| 143 string16* error) { |
| 144 WebDocument document = frame->document(); |
| 145 if (document.isNull()) |
| 146 return true; |
| 147 |
| 148 WebElement head = document.head(); |
| 149 if (head.isNull()) |
| 150 return true; |
| 151 |
| 152 GURL frame_url = frame->url(); |
| 153 WebNodeList children = head.childNodes(); |
| 154 for (unsigned i = 0; i < children.length(); ++i) { |
| 155 WebNode child = children.item(i); |
| 156 if (!child.isElementNode()) |
| 157 continue; |
| 158 WebElement elem = child.to<WebElement>(); |
| 159 |
| 160 if (elem.hasTagName("link")) { |
| 161 std::string rel = elem.getAttribute("rel").utf8(); |
| 162 // "rel" attribute may use either "icon" or "shortcut icon". |
| 163 // see also |
| 164 // <http://en.wikipedia.org/wiki/Favicon> |
| 165 // <http://dev.w3.org/html5/spec/Overview.html#rel-icon> |
| 166 if (LowerCaseEqualsASCII(rel, "icon") || |
| 167 LowerCaseEqualsASCII(rel, "shortcut icon")) { |
| 168 AddInstallIcon(elem, &app_info->icons); |
| 169 } else if (LowerCaseEqualsASCII(rel, "chrome-application-definition")) { |
| 170 std::string definition_url_string(elem.getAttribute("href").utf8()); |
| 171 GURL definition_url; |
| 172 if (!(definition_url = |
| 173 frame_url.Resolve(definition_url_string)).is_valid() || |
| 174 definition_url.GetOrigin() != frame_url.GetOrigin()) { |
| 175 *error = UTF8ToUTF16(WebApplicationInfo::kInvalidDefinitionURL); |
| 176 return false; |
| 177 } |
| 178 |
| 179 // If there is a definition file, all attributes come from it. |
| 180 *app_info = WebApplicationInfo(); |
| 181 app_info->manifest_url = definition_url; |
| 182 return true; |
| 183 } |
| 184 } else if (elem.hasTagName("meta") && elem.hasAttribute("name")) { |
| 185 std::string name = elem.getAttribute("name").utf8(); |
| 186 WebString content = elem.getAttribute("content"); |
| 187 if (name == "application-name") { |
| 188 app_info->title = content; |
| 189 } else if (name == "description") { |
| 190 app_info->description = content; |
| 191 } else if (name == "application-url") { |
| 192 std::string url = content.utf8(); |
| 193 app_info->app_url = frame_url.is_valid() ? |
| 194 frame_url.Resolve(url) : GURL(url); |
| 195 if (!app_info->app_url.is_valid()) |
| 196 app_info->app_url = GURL(); |
| 197 } |
| 198 } |
| 199 } |
| 200 |
| 201 return true; |
| 202 } |
| 203 |
| 204 bool ParseWebAppFromDefinitionFile(const DictionaryValue& definition, |
| 205 WebApplicationInfo* web_app, |
| 206 string16* error) { |
| 207 CHECK(web_app->manifest_url.is_valid()); |
| 208 |
| 209 int error_code = 0; |
| 210 std::string error_message; |
| 211 scoped_ptr<Value> schema( |
| 212 base::JSONReader::ReadAndReturnError( |
| 213 ResourceBundle::GetSharedInstance().GetRawDataResource( |
| 214 IDR_WEB_APP_SCHEMA).as_string(), |
| 215 false, // disallow trailing comma |
| 216 &error_code, |
| 217 &error_message)); |
| 218 CHECK(schema.get()) |
| 219 << "Error parsing JSON schema: " << error_code << ": " << error_message; |
| 220 CHECK(schema->IsType(Value::TYPE_DICTIONARY)) |
| 221 << "schema root must be dictionary."; |
| 222 |
| 223 JSONSchemaValidator validator(static_cast<DictionaryValue*>(schema.get())); |
| 224 |
| 225 // We allow extra properties in the schema for easy compat with other systems, |
| 226 // and for forward compat with ourselves. |
| 227 validator.set_default_allow_additional_properties(true); |
| 228 |
| 229 if (!validator.Validate(const_cast<DictionaryValue*>(&definition))) { |
| 230 *error = UTF8ToUTF16( |
| 231 validator.errors()[0].path + ": " + validator.errors()[0].message); |
| 232 return false; |
| 233 } |
| 234 |
| 235 // Parse launch URL. It must be a valid URL in the same origin as the |
| 236 // manifest. |
| 237 std::string app_url_string; |
| 238 GURL app_url; |
| 239 CHECK(definition.GetString("launch_url", &app_url_string)); |
| 240 if (!(app_url = web_app->manifest_url.Resolve(app_url_string)).is_valid() || |
| 241 app_url.GetOrigin() != web_app->manifest_url.GetOrigin()) { |
| 242 *error = UTF8ToUTF16(WebApplicationInfo::kInvalidLaunchURL); |
| 243 return false; |
| 244 } |
| 245 |
| 246 // Parse out the permissions if present. |
| 247 std::vector<std::string> permissions; |
| 248 ListValue* permissions_value = NULL; |
| 249 if (definition.GetList("permissions", &permissions_value)) { |
| 250 for (size_t i = 0; i < permissions_value->GetSize(); ++i) { |
| 251 std::string permission; |
| 252 CHECK(permissions_value->GetString(i, &permission)); |
| 253 permissions.push_back(permission); |
| 254 } |
| 255 } |
| 256 |
| 257 // Parse out the URLs if present. |
| 258 std::vector<GURL> urls; |
| 259 ListValue* urls_value = NULL; |
| 260 if (definition.GetList("urls", &urls_value)) { |
| 261 for (size_t i = 0; i < urls_value->GetSize(); ++i) { |
| 262 std::string url_string; |
| 263 GURL url; |
| 264 CHECK(urls_value->GetString(i, &url_string)); |
| 265 if (!(url = web_app->manifest_url.Resolve(url_string)).is_valid() || |
| 266 url.GetOrigin() != web_app->manifest_url.GetOrigin()) { |
| 267 *error = UTF8ToUTF16( |
| 268 JSONSchemaValidator::FormatErrorMessage( |
| 269 WebApplicationInfo::kInvalidURL, base::Uint64ToString(i))); |
| 270 return false; |
| 271 } |
| 272 urls.push_back(url); |
| 273 } |
| 274 } |
| 275 |
| 276 // Parse out the icons if present. |
| 277 std::vector<WebApplicationInfo::IconInfo> icons; |
| 278 DictionaryValue* icons_value = NULL; |
| 279 if (definition.GetDictionary("icons", &icons_value)) { |
| 280 for (DictionaryValue::key_iterator iter = icons_value->begin_keys(); |
| 281 iter != icons_value->end_keys(); ++iter) { |
| 282 // Ignore unknown properties. Better for forward compat. |
| 283 int size = 0; |
| 284 if (!base::StringToInt(*iter, &size) || size < 0 || size > 128) |
| 285 continue; |
| 286 |
| 287 std::string icon_url_string; |
| 288 GURL icon_url; |
| 289 if (!icons_value->GetString(*iter, &icon_url_string) || |
| 290 !(icon_url = web_app->manifest_url.Resolve( |
| 291 icon_url_string)).is_valid()) { |
| 292 *error = UTF8ToUTF16( |
| 293 JSONSchemaValidator::FormatErrorMessage( |
| 294 WebApplicationInfo::kInvalidIconURL, base::IntToString(size))); |
| 295 return false; |
| 296 } |
| 297 |
| 298 WebApplicationInfo::IconInfo icon; |
| 299 icon.url = icon_url; |
| 300 icon.width = size; |
| 301 icon.height = size; |
| 302 |
| 303 icons.push_back(icon); |
| 304 } |
| 305 } |
| 306 |
| 307 CHECK(definition.GetString("name", &web_app->title)); |
| 308 definition.GetString("description", &web_app->description); |
| 309 definition.GetString("launch_container", &web_app->launch_container); |
| 310 web_app->app_url = app_url; |
| 311 web_app->urls = urls; |
| 312 web_app->permissions = permissions; |
| 313 web_app->icons = icons; |
| 314 |
| 315 return true; |
| 316 |
| 317 } |
OLD | NEW |