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