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