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 "chrome/common/extensions/manifest_handlers/automation.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/common/extensions/api/manifest_types.h"
9#include "chrome/grit/generated_resources.h"
10#include "extensions/common/error_utils.h"
11#include "extensions/common/extensions_client.h"
12#include "extensions/common/manifest_constants.h"
13#include "extensions/common/permissions/api_permission_set.h"
14#include "extensions/common/permissions/manifest_permission.h"
15#include "extensions/common/permissions/permission_message.h"
16#include "extensions/common/permissions/permission_message_util.h"
17#include "extensions/common/permissions/permissions_data.h"
18#include "extensions/common/url_pattern.h"
19#include "ipc/ipc_message.h"
20#include "ipc/ipc_message_utils.h"
21#include "ui/base/l10n/l10n_util.h"
22
23namespace extensions {
24
25namespace automation_errors {
26const char kErrorDesktopTrueInteractFalse[] =
27    "Cannot specify interactive=false if desktop=true is specified; "
28    "interactive=false will be ignored.";
29const char kErrorDesktopTrueMatchesSpecified[] =
30    "Cannot specify matches for Automation if desktop=true is specified; "
31    "matches will be ignored.";
32const char kErrorInvalidMatch[] = "Invalid match pattern '*': *";
33const char kErrorNoMatchesProvided[] = "No valid match patterns provided.";
34}
35
36namespace errors = manifest_errors;
37namespace keys = extensions::manifest_keys;
38using api::manifest_types::Automation;
39
40class AutomationManifestPermission : public ManifestPermission {
41 public:
42  explicit AutomationManifestPermission(
43      scoped_ptr<const AutomationInfo> automation_info)
44      : automation_info_(automation_info.Pass()) {}
45
46  // extensions::ManifestPermission overrides.
47  virtual std::string name() const OVERRIDE;
48
49  virtual std::string id() const OVERRIDE;
50
51  virtual bool HasMessages() const OVERRIDE;
52
53  virtual PermissionMessages GetMessages() const OVERRIDE;
54
55  virtual bool FromValue(const base::Value* value) OVERRIDE;
56
57  virtual scoped_ptr<base::Value> ToValue() const OVERRIDE;
58
59  virtual ManifestPermission* Diff(
60      const ManifestPermission* rhs) const OVERRIDE;
61
62  virtual ManifestPermission* Union(
63      const ManifestPermission* rhs) const OVERRIDE;
64
65  virtual ManifestPermission* Intersect(
66      const ManifestPermission* rhs) const OVERRIDE;
67
68 private:
69  scoped_ptr<const AutomationInfo> automation_info_;
70};
71
72std::string AutomationManifestPermission::name() const {
73  return keys::kAutomation;
74}
75
76std::string AutomationManifestPermission::id() const {
77  return keys::kAutomation;
78}
79
80bool AutomationManifestPermission::HasMessages() const {
81  return GetMessages().size() > 0;
82}
83
84PermissionMessages AutomationManifestPermission::GetMessages() const {
85  PermissionMessages messages;
86  if (automation_info_->desktop) {
87    messages.push_back(PermissionMessage(
88        PermissionMessage::kFullAccess,
89        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS)));
90  } else if (automation_info_->matches.MatchesAllURLs()) {
91    messages.push_back(PermissionMessage(
92        PermissionMessage::kHostsAll,
93        l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS)));
94  } else {
95    URLPatternSet regular_hosts;
96    std::set<PermissionMessage> message_set;
97    ExtensionsClient::Get()->FilterHostPermissions(
98        automation_info_->matches, &regular_hosts, &message_set);
99    messages.insert(messages.end(), message_set.begin(), message_set.end());
100
101    std::set<std::string> hosts =
102        permission_message_util::GetDistinctHosts(regular_hosts, true, true);
103    if (!hosts.empty())
104      messages.push_back(permission_message_util::CreateFromHostList(hosts));
105  }
106
107  return messages;
108}
109
110bool AutomationManifestPermission::FromValue(const base::Value* value) {
111  base::string16 error;
112  automation_info_.reset(AutomationInfo::FromValue(*value,
113                                                   NULL /* install_warnings */,
114                                                   &error).release());
115  return error.empty();
116}
117
118scoped_ptr<base::Value> AutomationManifestPermission::ToValue() const {
119  return AutomationInfo::ToValue(*automation_info_).Pass();
120}
121
122ManifestPermission* AutomationManifestPermission::Diff(
123    const ManifestPermission* rhs) const {
124  const AutomationManifestPermission* other =
125      static_cast<const AutomationManifestPermission*>(rhs);
126
127  bool desktop = automation_info_->desktop && !other->automation_info_->desktop;
128  bool interact =
129      automation_info_->interact && !other->automation_info_->interact;
130  URLPatternSet matches;
131  URLPatternSet::CreateDifference(
132      automation_info_->matches, other->automation_info_->matches, &matches);
133  return new AutomationManifestPermission(
134      make_scoped_ptr(new const AutomationInfo(desktop, matches, interact)));
135}
136
137ManifestPermission* AutomationManifestPermission::Union(
138    const ManifestPermission* rhs) const {
139  const AutomationManifestPermission* other =
140      static_cast<const AutomationManifestPermission*>(rhs);
141
142  bool desktop = automation_info_->desktop || other->automation_info_->desktop;
143  bool interact =
144      automation_info_->interact || other->automation_info_->interact;
145  URLPatternSet matches;
146  URLPatternSet::CreateUnion(
147      automation_info_->matches, other->automation_info_->matches, &matches);
148  return new AutomationManifestPermission(
149      make_scoped_ptr(new const AutomationInfo(desktop, matches, interact)));
150}
151
152ManifestPermission* AutomationManifestPermission::Intersect(
153    const ManifestPermission* rhs) const {
154  const AutomationManifestPermission* other =
155      static_cast<const AutomationManifestPermission*>(rhs);
156
157  bool desktop = automation_info_->desktop && other->automation_info_->desktop;
158  bool interact =
159      automation_info_->interact && other->automation_info_->interact;
160  URLPatternSet matches;
161  URLPatternSet::CreateIntersection(
162      automation_info_->matches, other->automation_info_->matches, &matches);
163  return new AutomationManifestPermission(
164      make_scoped_ptr(new const AutomationInfo(desktop, matches, interact)));
165}
166
167AutomationHandler::AutomationHandler() {
168}
169
170AutomationHandler::~AutomationHandler() {
171}
172
173bool AutomationHandler::Parse(Extension* extension, base::string16* error) {
174  const base::Value* automation = NULL;
175  CHECK(extension->manifest()->Get(keys::kAutomation, &automation));
176  std::vector<InstallWarning> install_warnings;
177  scoped_ptr<AutomationInfo> info =
178      AutomationInfo::FromValue(*automation, &install_warnings, error);
179  if (!error->empty())
180    return false;
181
182  extension->AddInstallWarnings(install_warnings);
183
184  if (!info)
185    return true;
186
187  extension->SetManifestData(keys::kAutomation, info.release());
188  return true;
189}
190
191const std::vector<std::string> AutomationHandler::Keys() const {
192  return SingleKey(keys::kAutomation);
193}
194
195ManifestPermission* AutomationHandler::CreatePermission() {
196  return new AutomationManifestPermission(
197      make_scoped_ptr(new const AutomationInfo));
198}
199
200ManifestPermission* AutomationHandler::CreateInitialRequiredPermission(
201    const Extension* extension) {
202  const AutomationInfo* info = AutomationInfo::Get(extension);
203  if (info) {
204    return new AutomationManifestPermission(
205        make_scoped_ptr(new const AutomationInfo(
206            info->desktop, info->matches, info->interact)));
207  }
208  return NULL;
209}
210
211// static
212const AutomationInfo* AutomationInfo::Get(const Extension* extension) {
213  return static_cast<AutomationInfo*>(
214      extension->GetManifestData(keys::kAutomation));
215}
216
217// static
218scoped_ptr<AutomationInfo> AutomationInfo::FromValue(
219    const base::Value& value,
220    std::vector<InstallWarning>* install_warnings,
221    base::string16* error) {
222  scoped_ptr<Automation> automation = Automation::FromValue(value, error);
223  if (!automation)
224    return scoped_ptr<AutomationInfo>();
225
226  if (automation->as_boolean) {
227    if (*automation->as_boolean)
228      return make_scoped_ptr(new AutomationInfo());
229    return scoped_ptr<AutomationInfo>();
230  }
231  const Automation::Object& automation_object = *automation->as_object;
232
233  bool desktop = false;
234  bool interact = false;
235  if (automation_object.desktop && *automation_object.desktop) {
236    desktop = true;
237    interact = true;
238    if (automation_object.interact && !*automation_object.interact) {
239      // TODO(aboxhall): Do we want to allow this?
240      install_warnings->push_back(
241          InstallWarning(automation_errors::kErrorDesktopTrueInteractFalse));
242    }
243  } else if (automation_object.interact && *automation_object.interact) {
244    interact = true;
245  }
246
247  URLPatternSet matches;
248  bool specified_matches = false;
249  if (automation_object.matches) {
250    if (desktop) {
251      install_warnings->push_back(
252          InstallWarning(automation_errors::kErrorDesktopTrueMatchesSpecified));
253    } else {
254      specified_matches = true;
255
256      for (std::vector<std::string>::iterator it =
257               automation_object.matches->begin();
258           it != automation_object.matches->end();
259           ++it) {
260        // TODO(aboxhall): Refactor common logic from content_scripts_handler,
261        // manifest_url_handler and user_script.cc into a single location and
262        // re-use here.
263        URLPattern pattern(URLPattern::SCHEME_ALL &
264                           ~URLPattern::SCHEME_CHROMEUI);
265        URLPattern::ParseResult parse_result = pattern.Parse(*it);
266
267        if (parse_result != URLPattern::PARSE_SUCCESS) {
268          install_warnings->push_back(
269              InstallWarning(ErrorUtils::FormatErrorMessage(
270                  automation_errors::kErrorInvalidMatch,
271                  *it,
272                  URLPattern::GetParseResultString(parse_result))));
273          continue;
274        }
275
276        matches.AddPattern(pattern);
277      }
278    }
279  }
280  if (specified_matches && matches.is_empty()) {
281    install_warnings->push_back(
282        InstallWarning(automation_errors::kErrorNoMatchesProvided));
283  }
284
285  return make_scoped_ptr(new AutomationInfo(desktop, matches, interact));
286}
287
288// static
289scoped_ptr<base::Value> AutomationInfo::ToValue(const AutomationInfo& info) {
290  return AsManifestType(info)->ToValue().Pass();
291}
292
293// static
294scoped_ptr<Automation> AutomationInfo::AsManifestType(
295    const AutomationInfo& info) {
296  scoped_ptr<Automation> automation(new Automation);
297  if (!info.desktop && !info.interact && info.matches.size() == 0) {
298    automation->as_boolean.reset(new bool(true));
299    return automation.Pass();
300  }
301
302  Automation::Object* as_object = new Automation::Object;
303  as_object->desktop.reset(new bool(info.desktop));
304  as_object->interact.reset(new bool(info.interact));
305  if (info.matches.size() > 0) {
306    as_object->matches.reset(info.matches.ToStringVector().release());
307  }
308  automation->as_object.reset(as_object);
309  return automation.Pass();
310}
311
312AutomationInfo::AutomationInfo() : desktop(false), interact(false) {
313}
314
315AutomationInfo::AutomationInfo(bool desktop,
316                               const URLPatternSet matches,
317                               bool interact)
318    : desktop(desktop), matches(matches), interact(interact) {
319}
320
321AutomationInfo::~AutomationInfo() {
322}
323
324}  // namespace extensions
325