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 "extensions/common/manifest_handlers/shared_module_info.h"
6
7#include "base/lazy_instance.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/string_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/version.h"
13#include "components/crx_file/id_util.h"
14#include "extensions/common/constants.h"
15#include "extensions/common/error_utils.h"
16#include "extensions/common/manifest_constants.h"
17#include "extensions/common/permissions/permission_set.h"
18#include "extensions/common/permissions/permissions_data.h"
19
20namespace extensions {
21
22namespace keys = manifest_keys;
23namespace values = manifest_values;
24namespace errors = manifest_errors;
25
26namespace {
27
28const char kSharedModule[] = "shared_module";
29
30static base::LazyInstance<SharedModuleInfo> g_empty_shared_module_info =
31    LAZY_INSTANCE_INITIALIZER;
32
33const SharedModuleInfo& GetSharedModuleInfo(const Extension* extension) {
34  SharedModuleInfo* info = static_cast<SharedModuleInfo*>(
35      extension->GetManifestData(kSharedModule));
36  if (!info)
37    return g_empty_shared_module_info.Get();
38  return *info;
39}
40
41}  // namespace
42
43SharedModuleInfo::SharedModuleInfo() {
44}
45
46SharedModuleInfo::~SharedModuleInfo() {
47}
48
49// static
50void SharedModuleInfo::ParseImportedPath(const std::string& path,
51                                         std::string* import_id,
52                                         std::string* import_relative_path) {
53  std::vector<std::string> tokens;
54  Tokenize(path, std::string("/"), &tokens);
55  if (tokens.size() > 2 && tokens[0] == kModulesDir &&
56      crx_file::id_util::IdIsValid(tokens[1])) {
57    *import_id = tokens[1];
58    *import_relative_path = tokens[2];
59    for (size_t i = 3; i < tokens.size(); ++i)
60      *import_relative_path += "/" + tokens[i];
61  }
62}
63
64// static
65bool SharedModuleInfo::IsImportedPath(const std::string& path) {
66  std::vector<std::string> tokens;
67  Tokenize(path, std::string("/"), &tokens);
68  if (tokens.size() > 2 && tokens[0] == kModulesDir &&
69      crx_file::id_util::IdIsValid(tokens[1])) {
70    return true;
71  }
72  return false;
73}
74
75// static
76bool SharedModuleInfo::IsSharedModule(const Extension* extension) {
77  CHECK(extension);
78  return extension->manifest()->is_shared_module();
79}
80
81// static
82bool SharedModuleInfo::IsExportAllowed(const Extension* extension,
83                                       const std::string& relative_path) {
84  return GetSharedModuleInfo(extension).
85      exported_set_.MatchesURL(extension->url().Resolve(relative_path));
86}
87
88// static
89bool SharedModuleInfo::IsExportAllowedByWhitelist(const Extension* extension,
90                                                  const std::string& other_id) {
91  // Sanity check. In case the caller did not check |extension| to make sure it
92  // is a shared module, we do not want it to appear that the extension with
93  // |other_id| importing |extension| is valid.
94  if (!SharedModuleInfo::IsSharedModule(extension))
95    return false;
96  const SharedModuleInfo& info = GetSharedModuleInfo(extension);
97  if (info.export_whitelist_.empty())
98    return true;
99  if (info.export_whitelist_.find(other_id) != info.export_whitelist_.end())
100    return true;
101  return false;
102}
103
104// static
105bool SharedModuleInfo::ImportsExtensionById(const Extension* extension,
106                                            const std::string& other_id) {
107  const SharedModuleInfo& info = GetSharedModuleInfo(extension);
108  for (size_t i = 0; i < info.imports_.size(); i++) {
109    if (info.imports_[i].extension_id == other_id)
110      return true;
111  }
112  return false;
113}
114
115// static
116bool SharedModuleInfo::ImportsModules(const Extension* extension) {
117  return GetSharedModuleInfo(extension).imports_.size() > 0;
118}
119
120// static
121const std::vector<SharedModuleInfo::ImportInfo>& SharedModuleInfo::GetImports(
122    const Extension* extension) {
123  return GetSharedModuleInfo(extension).imports_;
124}
125
126bool SharedModuleInfo::Parse(const Extension* extension,
127                             base::string16* error) {
128  bool has_import = extension->manifest()->HasKey(keys::kImport);
129  bool has_export = extension->manifest()->HasKey(keys::kExport);
130  if (!has_import && !has_export)
131    return true;
132
133  if (has_import && has_export) {
134    *error = base::ASCIIToUTF16(errors::kInvalidImportAndExport);
135    return false;
136  }
137
138  if (has_export) {
139    const base::DictionaryValue* export_value = NULL;
140    if (!extension->manifest()->GetDictionary(keys::kExport, &export_value)) {
141      *error = base::ASCIIToUTF16(errors::kInvalidExport);
142      return false;
143    }
144    const base::ListValue* resources_list = NULL;
145    if (!export_value->GetList(keys::kResources, &resources_list)) {
146      *error = base::ASCIIToUTF16(errors::kInvalidExportResources);
147      return false;
148    }
149    if (export_value->HasKey(keys::kWhitelist)) {
150      const base::ListValue* whitelist = NULL;
151      if (!export_value->GetList(keys::kWhitelist, &whitelist)) {
152        *error = base::ASCIIToUTF16(errors::kInvalidExportWhitelist);
153        return false;
154      }
155      for (size_t i = 0; i < whitelist->GetSize(); ++i) {
156        std::string extension_id;
157        if (!whitelist->GetString(i, &extension_id) ||
158            !crx_file::id_util::IdIsValid(extension_id)) {
159          *error = ErrorUtils::FormatErrorMessageUTF16(
160              errors::kInvalidExportWhitelistString, base::IntToString(i));
161          return false;
162        }
163        export_whitelist_.insert(extension_id);
164      }
165    }
166    for (size_t i = 0; i < resources_list->GetSize(); ++i) {
167      std::string resource_path;
168      if (!resources_list->GetString(i, &resource_path)) {
169        *error = ErrorUtils::FormatErrorMessageUTF16(
170            errors::kInvalidExportResourcesString, base::IntToString(i));
171        return false;
172      }
173      const GURL& resolved_path = extension->url().Resolve(resource_path);
174      if (!resolved_path.is_valid()) {
175        *error = ErrorUtils::FormatErrorMessageUTF16(
176            errors::kInvalidExportResourcesString, base::IntToString(i));
177        return false;
178      }
179      exported_set_.AddPattern(
180          URLPattern(URLPattern::SCHEME_EXTENSION, resolved_path.spec()));
181    }
182  }
183
184  if (has_import) {
185    const base::ListValue* import_list = NULL;
186    if (!extension->manifest()->GetList(keys::kImport, &import_list)) {
187      *error = base::ASCIIToUTF16(errors::kInvalidImport);
188      return false;
189    }
190    for (size_t i = 0; i < import_list->GetSize(); ++i) {
191      const base::DictionaryValue* import_entry = NULL;
192      if (!import_list->GetDictionary(i, &import_entry)) {
193        *error = base::ASCIIToUTF16(errors::kInvalidImport);
194        return false;
195      }
196      std::string extension_id;
197      imports_.push_back(ImportInfo());
198      if (!import_entry->GetString(keys::kId, &extension_id) ||
199          !crx_file::id_util::IdIsValid(extension_id)) {
200        *error = ErrorUtils::FormatErrorMessageUTF16(
201            errors::kInvalidImportId, base::IntToString(i));
202        return false;
203      }
204      imports_.back().extension_id = extension_id;
205      if (import_entry->HasKey(keys::kMinimumVersion)) {
206        std::string min_version;
207        if (!import_entry->GetString(keys::kMinimumVersion, &min_version)) {
208          *error = ErrorUtils::FormatErrorMessageUTF16(
209              errors::kInvalidImportVersion, base::IntToString(i));
210          return false;
211        }
212        imports_.back().minimum_version = min_version;
213        Version v(min_version);
214        if (!v.IsValid()) {
215          *error = ErrorUtils::FormatErrorMessageUTF16(
216              errors::kInvalidImportVersion, base::IntToString(i));
217          return false;
218        }
219      }
220    }
221  }
222  return true;
223}
224
225
226SharedModuleHandler::SharedModuleHandler() {
227}
228
229SharedModuleHandler::~SharedModuleHandler() {
230}
231
232bool SharedModuleHandler::Parse(Extension* extension, base::string16* error) {
233  scoped_ptr<SharedModuleInfo> info(new SharedModuleInfo);
234  if (!info->Parse(extension, error))
235    return false;
236  extension->SetManifestData(kSharedModule, info.release());
237  return true;
238}
239
240bool SharedModuleHandler::Validate(
241    const Extension* extension,
242    std::string* error,
243    std::vector<InstallWarning>* warnings) const {
244  // Extensions that export resources should not have any permissions of their
245  // own, instead they rely on the permissions of the extensions which import
246  // them.
247  if (SharedModuleInfo::IsSharedModule(extension) &&
248      !extension->permissions_data()->active_permissions()->IsEmpty()) {
249    *error = errors::kInvalidExportPermissions;
250    return false;
251  }
252  return true;
253}
254
255const std::vector<std::string> SharedModuleHandler::Keys() const {
256  static const char* keys[] = {
257    keys::kExport,
258    keys::kImport
259  };
260  return std::vector<std::string>(keys, keys + arraysize(keys));
261}
262
263}  // namespace extensions
264