1 // Copyright 2013 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/extensions/manifest_handlers/app_launch_info.h" 6 7 #include "base/command_line.h" 8 #include "base/lazy_instance.h" 9 #include "base/memory/scoped_ptr.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "base/values.h" 12 #include "chrome/common/chrome_switches.h" 13 #include "chrome/common/extensions/extension_constants.h" 14 #include "chrome/common/url_constants.h" 15 #include "components/cloud_devices/common/cloud_devices_urls.h" 16 #include "extensions/common/constants.h" 17 #include "extensions/common/error_utils.h" 18 #include "extensions/common/manifest_constants.h" 19 20 namespace extensions { 21 22 namespace keys = manifest_keys; 23 namespace values = manifest_values; 24 namespace errors = manifest_errors; 25 26 namespace { 27 28 bool ReadLaunchDimension(const extensions::Manifest* manifest, 29 const char* key, 30 int* target, 31 bool is_valid_container, 32 base::string16* error) { 33 const base::Value* temp = NULL; 34 if (manifest->Get(key, &temp)) { 35 if (!is_valid_container) { 36 *error = ErrorUtils::FormatErrorMessageUTF16( 37 errors::kInvalidLaunchValueContainer, 38 key); 39 return false; 40 } 41 if (!temp->GetAsInteger(target) || *target < 0) { 42 *target = 0; 43 *error = ErrorUtils::FormatErrorMessageUTF16( 44 errors::kInvalidLaunchValue, 45 key); 46 return false; 47 } 48 } 49 return true; 50 } 51 52 static base::LazyInstance<AppLaunchInfo> g_empty_app_launch_info = 53 LAZY_INSTANCE_INITIALIZER; 54 55 const AppLaunchInfo& GetAppLaunchInfo(const Extension* extension) { 56 AppLaunchInfo* info = static_cast<AppLaunchInfo*>( 57 extension->GetManifestData(keys::kLaunch)); 58 return info ? *info : g_empty_app_launch_info.Get(); 59 } 60 61 } // namespace 62 63 AppLaunchInfo::AppLaunchInfo() 64 : launch_container_(LAUNCH_CONTAINER_TAB), 65 launch_width_(0), 66 launch_height_(0) { 67 } 68 69 AppLaunchInfo::~AppLaunchInfo() { 70 } 71 72 // static 73 const std::string& AppLaunchInfo::GetLaunchLocalPath( 74 const Extension* extension) { 75 return GetAppLaunchInfo(extension).launch_local_path_; 76 } 77 78 // static 79 const GURL& AppLaunchInfo::GetLaunchWebURL( 80 const Extension* extension) { 81 return GetAppLaunchInfo(extension).launch_web_url_; 82 } 83 84 // static 85 extensions::LaunchContainer AppLaunchInfo::GetLaunchContainer( 86 const Extension* extension) { 87 return GetAppLaunchInfo(extension).launch_container_; 88 } 89 90 // static 91 int AppLaunchInfo::GetLaunchWidth(const Extension* extension) { 92 return GetAppLaunchInfo(extension).launch_width_; 93 } 94 95 // static 96 int AppLaunchInfo::GetLaunchHeight(const Extension* extension) { 97 return GetAppLaunchInfo(extension).launch_height_; 98 } 99 100 // static 101 GURL AppLaunchInfo::GetFullLaunchURL(const Extension* extension) { 102 const AppLaunchInfo& info = GetAppLaunchInfo(extension); 103 if (info.launch_local_path_.empty()) 104 return info.launch_web_url_; 105 else 106 return extension->url().Resolve(info.launch_local_path_); 107 } 108 109 bool AppLaunchInfo::Parse(Extension* extension, base::string16* error) { 110 if (!LoadLaunchURL(extension, error) || 111 !LoadLaunchContainer(extension, error)) 112 return false; 113 return true; 114 } 115 116 bool AppLaunchInfo::LoadLaunchURL(Extension* extension, base::string16* error) { 117 const base::Value* temp = NULL; 118 119 // Launch URL can be either local (to chrome-extension:// root) or an absolute 120 // web URL. 121 if (extension->manifest()->Get(keys::kLaunchLocalPath, &temp)) { 122 if (extension->manifest()->Get(keys::kLaunchWebURL, NULL)) { 123 *error = base::ASCIIToUTF16(errors::kLaunchPathAndURLAreExclusive); 124 return false; 125 } 126 127 if (extension->manifest()->Get(keys::kWebURLs, NULL)) { 128 *error = base::ASCIIToUTF16(errors::kLaunchPathAndExtentAreExclusive); 129 return false; 130 } 131 132 std::string launch_path; 133 if (!temp->GetAsString(&launch_path)) { 134 *error = ErrorUtils::FormatErrorMessageUTF16( 135 errors::kInvalidLaunchValue, 136 keys::kLaunchLocalPath); 137 return false; 138 } 139 140 // Ensure the launch path is a valid relative URL. 141 GURL resolved = extension->url().Resolve(launch_path); 142 if (!resolved.is_valid() || resolved.GetOrigin() != extension->url()) { 143 *error = ErrorUtils::FormatErrorMessageUTF16( 144 errors::kInvalidLaunchValue, 145 keys::kLaunchLocalPath); 146 return false; 147 } 148 149 launch_local_path_ = launch_path; 150 } else if (extension->manifest()->Get(keys::kLaunchWebURL, &temp)) { 151 std::string launch_url; 152 if (!temp->GetAsString(&launch_url)) { 153 *error = ErrorUtils::FormatErrorMessageUTF16( 154 errors::kInvalidLaunchValue, 155 keys::kLaunchWebURL); 156 return false; 157 } 158 159 // Ensure the launch web URL is a valid absolute URL and web extent scheme. 160 GURL url(launch_url); 161 URLPattern pattern(Extension::kValidWebExtentSchemes); 162 if (!url.is_valid() || !pattern.SetScheme(url.scheme())) { 163 *error = ErrorUtils::FormatErrorMessageUTF16( 164 errors::kInvalidLaunchValue, 165 keys::kLaunchWebURL); 166 return false; 167 } 168 169 launch_web_url_ = url; 170 } else if (extension->is_legacy_packaged_app()) { 171 *error = base::ASCIIToUTF16(errors::kLaunchURLRequired); 172 return false; 173 } 174 175 // For the Chrome component app, override launch url to new tab. 176 if (extension->id() == extension_misc::kChromeAppId) { 177 launch_web_url_ = GURL(chrome::kChromeUINewTabURL); 178 return true; 179 } 180 181 // If there is no extent, we default the extent based on the launch URL. 182 if (extension->web_extent().is_empty() && !launch_web_url_.is_empty()) { 183 URLPattern pattern(Extension::kValidWebExtentSchemes); 184 if (!pattern.SetScheme("*")) { 185 *error = ErrorUtils::FormatErrorMessageUTF16( 186 errors::kInvalidLaunchValue, 187 keys::kLaunchWebURL); 188 return false; 189 } 190 pattern.SetHost(launch_web_url_.host()); 191 pattern.SetPath("/*"); 192 extension->AddWebExtentPattern(pattern); 193 } 194 195 // In order for the --apps-gallery-url switch to work with the gallery 196 // process isolation, we must insert any provided value into the component 197 // app's launch url and web extent. 198 if (extension->id() == extensions::kWebStoreAppId) { 199 std::string gallery_url_str = CommandLine::ForCurrentProcess()-> 200 GetSwitchValueASCII(switches::kAppsGalleryURL); 201 202 // Empty string means option was not used. 203 if (!gallery_url_str.empty()) { 204 GURL gallery_url(gallery_url_str); 205 OverrideLaunchURL(extension, gallery_url); 206 } 207 } else if (extension->id() == extension_misc::kCloudPrintAppId) { 208 // In order for the --cloud-print-service switch to work, we must update 209 // the launch URL and web extent. 210 GURL url = 211 cloud_devices::GetCloudPrintRelativeURL("enable_chrome_connector"); 212 if (!url.is_empty()) { 213 OverrideLaunchURL(extension, url); 214 } 215 } 216 217 return true; 218 } 219 220 bool AppLaunchInfo::LoadLaunchContainer(Extension* extension, 221 base::string16* error) { 222 const base::Value* tmp_launcher_container = NULL; 223 if (!extension->manifest()->Get(keys::kLaunchContainer, 224 &tmp_launcher_container)) 225 return true; 226 227 std::string launch_container_string; 228 if (!tmp_launcher_container->GetAsString(&launch_container_string)) { 229 *error = base::ASCIIToUTF16(errors::kInvalidLaunchContainer); 230 return false; 231 } 232 233 if (launch_container_string == values::kLaunchContainerPanel) { 234 launch_container_ = LAUNCH_CONTAINER_PANEL; 235 } else if (launch_container_string == values::kLaunchContainerTab) { 236 launch_container_ = LAUNCH_CONTAINER_TAB; 237 } else { 238 *error = base::ASCIIToUTF16(errors::kInvalidLaunchContainer); 239 return false; 240 } 241 242 bool can_specify_initial_size = launch_container_ == LAUNCH_CONTAINER_PANEL; 243 244 // Validate the container width if present. 245 if (!ReadLaunchDimension(extension->manifest(), 246 keys::kLaunchWidth, 247 &launch_width_, 248 can_specify_initial_size, 249 error)) { 250 return false; 251 } 252 253 // Validate container height if present. 254 if (!ReadLaunchDimension(extension->manifest(), 255 keys::kLaunchHeight, 256 &launch_height_, 257 can_specify_initial_size, 258 error)) { 259 return false; 260 } 261 262 return true; 263 } 264 265 void AppLaunchInfo::OverrideLaunchURL(Extension* extension, 266 GURL override_url) { 267 if (!override_url.is_valid()) { 268 DLOG(WARNING) << "Invalid override url given for " << extension->name(); 269 return; 270 } 271 if (override_url.has_port()) { 272 DLOG(WARNING) << "Override URL passed for " << extension->name() 273 << " should not contain a port. Removing it."; 274 275 GURL::Replacements remove_port; 276 remove_port.ClearPort(); 277 override_url = override_url.ReplaceComponents(remove_port); 278 } 279 280 launch_web_url_ = override_url; 281 282 URLPattern pattern(Extension::kValidWebExtentSchemes); 283 URLPattern::ParseResult result = pattern.Parse(override_url.spec()); 284 DCHECK_EQ(result, URLPattern::PARSE_SUCCESS); 285 pattern.SetPath(pattern.path() + '*'); 286 extension->AddWebExtentPattern(pattern); 287 } 288 289 AppLaunchManifestHandler::AppLaunchManifestHandler() { 290 } 291 292 AppLaunchManifestHandler::~AppLaunchManifestHandler() { 293 } 294 295 bool AppLaunchManifestHandler::Parse(Extension* extension, 296 base::string16* error) { 297 scoped_ptr<AppLaunchInfo> info(new AppLaunchInfo); 298 if (!info->Parse(extension, error)) 299 return false; 300 extension->SetManifestData(keys::kLaunch, info.release()); 301 return true; 302 } 303 304 bool AppLaunchManifestHandler::AlwaysParseForType(Manifest::Type type) const { 305 return type == Manifest::TYPE_LEGACY_PACKAGED_APP; 306 } 307 308 const std::vector<std::string> AppLaunchManifestHandler::Keys() const { 309 static const char* keys[] = { 310 keys::kLaunchLocalPath, 311 keys::kLaunchWebURL, 312 keys::kLaunchContainer, 313 keys::kLaunchHeight, 314 keys::kLaunchWidth 315 }; 316 return std::vector<std::string>(keys, keys + arraysize(keys)); 317 } 318 319 } // namespace extensions 320