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
20namespace extensions {
21
22namespace keys = manifest_keys;
23namespace values = manifest_values;
24namespace errors = manifest_errors;
25
26namespace {
27
28bool 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
52static base::LazyInstance<AppLaunchInfo> g_empty_app_launch_info =
53    LAZY_INSTANCE_INITIALIZER;
54
55const 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
63AppLaunchInfo::AppLaunchInfo()
64    : launch_container_(LAUNCH_CONTAINER_TAB),
65      launch_width_(0),
66      launch_height_(0) {
67}
68
69AppLaunchInfo::~AppLaunchInfo() {
70}
71
72// static
73const std::string& AppLaunchInfo::GetLaunchLocalPath(
74    const Extension* extension) {
75  return GetAppLaunchInfo(extension).launch_local_path_;
76}
77
78// static
79const GURL& AppLaunchInfo::GetLaunchWebURL(
80    const Extension* extension) {
81  return GetAppLaunchInfo(extension).launch_web_url_;
82}
83
84// static
85extensions::LaunchContainer AppLaunchInfo::GetLaunchContainer(
86    const Extension* extension) {
87  return GetAppLaunchInfo(extension).launch_container_;
88}
89
90// static
91int AppLaunchInfo::GetLaunchWidth(const Extension* extension) {
92  return GetAppLaunchInfo(extension).launch_width_;
93}
94
95// static
96int AppLaunchInfo::GetLaunchHeight(const Extension* extension) {
97  return GetAppLaunchInfo(extension).launch_height_;
98}
99
100// static
101GURL 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
109bool AppLaunchInfo::Parse(Extension* extension, base::string16* error) {
110  if (!LoadLaunchURL(extension, error) ||
111      !LoadLaunchContainer(extension, error))
112    return false;
113  return true;
114}
115
116bool 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
220bool 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
265void 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
289AppLaunchManifestHandler::AppLaunchManifestHandler() {
290}
291
292AppLaunchManifestHandler::~AppLaunchManifestHandler() {
293}
294
295bool 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
304bool AppLaunchManifestHandler::AlwaysParseForType(Manifest::Type type) const {
305  return type == Manifest::TYPE_LEGACY_PACKAGED_APP;
306}
307
308const 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