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
ReadLaunchDimension(const extensions::Manifest * manifest,const char * key,int * target,bool is_valid_container,base::string16 * error)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
GetAppLaunchInfo(const Extension * extension)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
AppLaunchInfo()63 AppLaunchInfo::AppLaunchInfo()
64 : launch_container_(LAUNCH_CONTAINER_TAB),
65 launch_width_(0),
66 launch_height_(0) {
67 }
68
~AppLaunchInfo()69 AppLaunchInfo::~AppLaunchInfo() {
70 }
71
72 // static
GetLaunchLocalPath(const Extension * extension)73 const std::string& AppLaunchInfo::GetLaunchLocalPath(
74 const Extension* extension) {
75 return GetAppLaunchInfo(extension).launch_local_path_;
76 }
77
78 // static
GetLaunchWebURL(const Extension * extension)79 const GURL& AppLaunchInfo::GetLaunchWebURL(
80 const Extension* extension) {
81 return GetAppLaunchInfo(extension).launch_web_url_;
82 }
83
84 // static
GetLaunchContainer(const Extension * extension)85 extensions::LaunchContainer AppLaunchInfo::GetLaunchContainer(
86 const Extension* extension) {
87 return GetAppLaunchInfo(extension).launch_container_;
88 }
89
90 // static
GetLaunchWidth(const Extension * extension)91 int AppLaunchInfo::GetLaunchWidth(const Extension* extension) {
92 return GetAppLaunchInfo(extension).launch_width_;
93 }
94
95 // static
GetLaunchHeight(const Extension * extension)96 int AppLaunchInfo::GetLaunchHeight(const Extension* extension) {
97 return GetAppLaunchInfo(extension).launch_height_;
98 }
99
100 // static
GetFullLaunchURL(const Extension * extension)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
Parse(Extension * extension,base::string16 * error)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
LoadLaunchURL(Extension * extension,base::string16 * error)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
LoadLaunchContainer(Extension * extension,base::string16 * error)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
OverrideLaunchURL(Extension * extension,GURL override_url)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
AppLaunchManifestHandler()289 AppLaunchManifestHandler::AppLaunchManifestHandler() {
290 }
291
~AppLaunchManifestHandler()292 AppLaunchManifestHandler::~AppLaunchManifestHandler() {
293 }
294
Parse(Extension * extension,base::string16 * error)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
AlwaysParseForType(Manifest::Type type) const304 bool AppLaunchManifestHandler::AlwaysParseForType(Manifest::Type type) const {
305 return type == Manifest::TYPE_LEGACY_PACKAGED_APP;
306 }
307
Keys() const308 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