1// Copyright 2014 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 "extensions/common/manifest_handlers/options_page_info.h"
6
7#include "base/files/file_util.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/strings/utf_string_conversions.h"
10#include "extensions/common/api/extensions_manifest_types.h"
11#include "extensions/common/error_utils.h"
12#include "extensions/common/feature_switch.h"
13#include "extensions/common/file_util.h"
14#include "extensions/common/manifest_constants.h"
15#include "extensions/strings/grit/extensions_strings.h"
16#include "ui/base/l10n/l10n_util.h"
17
18using base::ASCIIToUTF16;
19using base::DictionaryValue;
20
21namespace extensions {
22
23namespace keys = manifest_keys;
24namespace errors = manifest_errors;
25
26using core_api::extensions_manifest_types::OptionsUI;
27
28namespace {
29
30OptionsPageInfo* GetOptionsPageInfo(const Extension* extension) {
31  return static_cast<OptionsPageInfo*>(
32      extension->GetManifestData(keys::kOptionsUI));
33}
34
35// Parses |url_string| into a GURL |result| if it is a valid options page for
36// this app/extension. If not, it returns the reason in |error|. Because this
37// handles URLs for both "options_page" and "options_ui.page", the name of the
38// manifest field must be provided in |manifest_field_name|.
39bool ParseOptionsUrl(Extension* extension,
40                     const std::string& url_string,
41                     const std::string& manifest_field_name,
42                     base::string16* error,
43                     GURL* result) {
44  if (extension->is_hosted_app()) {
45    // Hosted apps require an absolute URL.
46    GURL options_url(url_string);
47    if (!options_url.is_valid() || !options_url.SchemeIsHTTPOrHTTPS()) {
48      *error = base::ASCIIToUTF16(errors::kInvalidOptionsPageInHostedApp);
49      return false;
50    }
51    *result = options_url;
52    return true;
53  }
54
55  // Otherwise the options URL should be inside the extension.
56  if (GURL(url_string).is_valid()) {
57    *error = base::ASCIIToUTF16(errors::kInvalidOptionsPageExpectUrlInPackage);
58    return false;
59  }
60
61  GURL resource_url = extension->GetResourceURL(url_string);
62  if (!resource_url.is_valid()) {
63    *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidOptionsPage,
64                                                 manifest_field_name);
65    return false;
66  }
67  *result = resource_url;
68  return true;
69}
70
71}  // namespace
72
73OptionsPageInfo::OptionsPageInfo(const GURL& options_page,
74                                 bool chrome_styles,
75                                 bool open_in_tab)
76    : options_page_(options_page),
77      chrome_styles_(chrome_styles),
78      open_in_tab_(open_in_tab) {
79}
80
81OptionsPageInfo::~OptionsPageInfo() {
82}
83
84// static
85const GURL& OptionsPageInfo::GetOptionsPage(const Extension* extension) {
86  OptionsPageInfo* info = GetOptionsPageInfo(extension);
87  return info ? info->options_page_ : GURL::EmptyGURL();
88}
89
90// static
91bool OptionsPageInfo::HasOptionsPage(const Extension* extension) {
92  return !OptionsPageInfo::GetOptionsPage(extension).is_empty();
93}
94
95// static
96bool OptionsPageInfo::ShouldUseChromeStyle(const Extension* extension) {
97  OptionsPageInfo* info = GetOptionsPageInfo(extension);
98  return info && info->chrome_styles_;
99}
100
101// static
102bool OptionsPageInfo::ShouldOpenInTab(const Extension* extension) {
103  OptionsPageInfo* info = GetOptionsPageInfo(extension);
104  return info && info->open_in_tab_;
105}
106
107scoped_ptr<OptionsPageInfo> OptionsPageInfo::Create(
108    Extension* extension,
109    const base::Value* options_ui_value,
110    const std::string& options_page_string,
111    std::vector<InstallWarning>* install_warnings,
112    base::string16* error) {
113  GURL options_page;
114  bool chrome_style = false;
115  bool open_in_tab = !FeatureSwitch::embedded_extension_options()->IsEnabled();
116
117  // Parse the options_ui object.
118  if (options_ui_value &&
119      FeatureSwitch::embedded_extension_options()->IsEnabled()) {
120    base::string16 options_ui_error;
121
122    scoped_ptr<OptionsUI> options_ui =
123        OptionsUI::FromValue(*options_ui_value, &options_ui_error);
124    if (!options_ui_error.empty()) {
125      // OptionsUI::FromValue populates |error| both when there are
126      // errors (in which case |options_ui| will be NULL) and warnings
127      // (in which case |options_ui| will be valid). Either way, show it
128      // as an install warning.
129      install_warnings->push_back(
130          InstallWarning(base::UTF16ToASCII(options_ui_error)));
131    }
132
133    if (options_ui) {
134      base::string16 options_parse_error;
135      if (!ParseOptionsUrl(extension,
136                           options_ui->page,
137                           keys::kOptionsUI,
138                           &options_parse_error,
139                           &options_page)) {
140        install_warnings->push_back(
141            InstallWarning(base::UTF16ToASCII(options_parse_error)));
142      }
143      chrome_style =
144          options_ui->chrome_style.get() && *options_ui->chrome_style;
145      open_in_tab = options_ui->open_in_tab.get() && *options_ui->open_in_tab;
146    }
147  }
148
149  // Parse the legacy options_page entry if there was no entry for
150  // options_ui.page.
151  if (!options_page_string.empty() && !options_page.is_valid()) {
152    if (!ParseOptionsUrl(extension,
153                         options_page_string,
154                         keys::kOptionsPage,
155                         error,
156                         &options_page)) {
157      return scoped_ptr<OptionsPageInfo>();
158    }
159  }
160
161  return make_scoped_ptr(
162      new OptionsPageInfo(options_page, chrome_style, open_in_tab));
163}
164
165OptionsPageManifestHandler::OptionsPageManifestHandler() {
166}
167
168OptionsPageManifestHandler::~OptionsPageManifestHandler() {
169}
170
171bool OptionsPageManifestHandler::Parse(Extension* extension,
172                                       base::string16* error) {
173  std::vector<InstallWarning> install_warnings;
174  const Manifest* manifest = extension->manifest();
175
176  std::string options_page_string;
177  if (manifest->HasPath(keys::kOptionsPage) &&
178      !manifest->GetString(keys::kOptionsPage, &options_page_string)) {
179    *error = ErrorUtils::FormatErrorMessageUTF16(errors::kInvalidOptionsPage,
180                                                 keys::kOptionsPage);
181    return false;
182  }
183
184  const base::Value* options_ui_value = NULL;
185  ignore_result(manifest->Get(keys::kOptionsUI, &options_ui_value));
186
187  scoped_ptr<OptionsPageInfo> info =
188      OptionsPageInfo::Create(extension,
189                              options_ui_value,
190                              options_page_string,
191                              &install_warnings,
192                              error);
193  if (!info)
194    return false;
195
196  extension->AddInstallWarnings(install_warnings);
197  extension->SetManifestData(keys::kOptionsUI, info.release());
198  return true;
199}
200
201bool OptionsPageManifestHandler::Validate(
202    const Extension* extension,
203    std::string* error,
204    std::vector<InstallWarning>* warnings) const {
205  // Validate path to the options page.  Don't check the URL for hosted apps,
206  // because they are expected to refer to an external URL.
207  if (!OptionsPageInfo::HasOptionsPage(extension) || extension->is_hosted_app())
208    return true;
209
210  base::FilePath options_path = file_util::ExtensionURLToRelativeFilePath(
211      OptionsPageInfo::GetOptionsPage(extension));
212  base::FilePath path = extension->GetResource(options_path).GetFilePath();
213  if (path.empty() || !base::PathExists(path)) {
214    *error = l10n_util::GetStringFUTF8(IDS_EXTENSION_LOAD_OPTIONS_PAGE_FAILED,
215                                       options_path.LossyDisplayName());
216    return false;
217  }
218  return true;
219}
220
221const std::vector<std::string> OptionsPageManifestHandler::Keys() const {
222  static const char* keys[] = {keys::kOptionsPage, keys::kOptionsUI};
223  return std::vector<std::string>(keys, keys + arraysize(keys));
224}
225
226}  // namespace extensions
227