1// Copyright (c) 2011 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/browser/extensions/external_extension_provider_impl.h"
6
7#include "app/app_paths.h"
8#include "base/file_path.h"
9#include "base/logging.h"
10#include "base/memory/linked_ptr.h"
11#include "base/path_service.h"
12#include "base/values.h"
13#include "base/version.h"
14#include "chrome/browser/extensions/external_extension_provider_interface.h"
15#include "chrome/browser/extensions/external_policy_extension_loader.h"
16#include "chrome/browser/extensions/external_pref_extension_loader.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/common/chrome_paths.h"
19#include "content/browser/browser_thread.h"
20
21#if defined(OS_WIN)
22#include "chrome/browser/extensions/external_registry_extension_loader_win.h"
23#endif
24
25// Constants for keeping track of extension preferences in a dictionary.
26const char ExternalExtensionProviderImpl::kLocation[] = "location";
27const char ExternalExtensionProviderImpl::kState[] = "state";
28const char ExternalExtensionProviderImpl::kExternalCrx[] = "external_crx";
29const char ExternalExtensionProviderImpl::kExternalVersion[] =
30    "external_version";
31const char ExternalExtensionProviderImpl::kExternalUpdateUrl[] =
32    "external_update_url";
33
34ExternalExtensionProviderImpl::ExternalExtensionProviderImpl(
35    VisitorInterface* service,
36    ExternalExtensionLoader* loader,
37    Extension::Location crx_location,
38    Extension::Location download_location)
39  : crx_location_(crx_location),
40    download_location_(download_location),
41    service_(service),
42    prefs_(NULL),
43    ready_(false),
44    loader_(loader) {
45  loader_->Init(this);
46}
47
48ExternalExtensionProviderImpl::~ExternalExtensionProviderImpl() {
49  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
50  loader_->OwnerShutdown();
51}
52
53void ExternalExtensionProviderImpl::VisitRegisteredExtension() const {
54  // The loader will call back to SetPrefs.
55  loader_->StartLoading();
56}
57
58void ExternalExtensionProviderImpl::SetPrefs(DictionaryValue* prefs) {
59  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
60
61  // Check if the service is still alive. It is possible that it had went
62  // away while |loader_| was working on the FILE thread.
63  if (!service_) return;
64
65  prefs_.reset(prefs);
66  ready_ = true; // Queries for extensions are allowed from this point.
67
68  // Notify ExtensionService about all the extensions this provider has.
69  for (DictionaryValue::key_iterator i = prefs_->begin_keys();
70       i != prefs_->end_keys(); ++i) {
71    const std::string& extension_id = *i;
72    DictionaryValue* extension;
73
74    if (!Extension::IdIsValid(extension_id)) {
75      LOG(WARNING) << "Malformed extension dictionary: key "
76                   << extension_id.c_str() << " is not a valid id.";
77      continue;
78    }
79
80    if (!prefs_->GetDictionaryWithoutPathExpansion(extension_id, &extension)) {
81      LOG(WARNING) << "Malformed extension dictionary: key "
82                   << extension_id.c_str()
83                   << " has a value that is not a dictionary.";
84      continue;
85    }
86
87    FilePath::StringType external_crx;
88    std::string external_version;
89    std::string external_update_url;
90
91    bool has_external_crx = extension->GetString(kExternalCrx, &external_crx);
92    bool has_external_version = extension->GetString(kExternalVersion,
93                                                     &external_version);
94    bool has_external_update_url = extension->GetString(kExternalUpdateUrl,
95                                                        &external_update_url);
96    if (has_external_crx != has_external_version) {
97      LOG(WARNING) << "Malformed extension dictionary for extension: "
98                   << extension_id.c_str() << ".  " << kExternalCrx
99                   << " and " << kExternalVersion << " must be used together.";
100      continue;
101    }
102
103    if (has_external_crx == has_external_update_url) {
104      LOG(WARNING) << "Malformed extension dictionary for extension: "
105                   << extension_id.c_str() << ".  Exactly one of the "
106                   << "followng keys should be used: " << kExternalCrx
107                   << ", " << kExternalUpdateUrl << ".";
108      continue;
109    }
110
111    if (has_external_crx) {
112      if (crx_location_ == Extension::INVALID) {
113        LOG(WARNING) << "This provider does not support installing external "
114                     << "extensions from crx files.";
115        continue;
116      }
117      if (external_crx.find(FilePath::kParentDirectory) !=
118          base::StringPiece::npos) {
119        LOG(WARNING) << "Path traversal not allowed in path: "
120                     << external_crx.c_str();
121        continue;
122      }
123
124      // If the path is relative, and the provider has a base path,
125      // build the absolute path to the crx file.
126      FilePath path(external_crx);
127      if (!path.IsAbsolute()) {
128        FilePath base_path = loader_->GetBaseCrxFilePath();
129        if (base_path.empty()) {
130          LOG(WARNING) << "File path " << external_crx.c_str()
131                       << " is relative.  An absolute path is required.";
132          continue;
133        }
134        path = base_path.Append(external_crx);
135      }
136
137      scoped_ptr<Version> version;
138      version.reset(Version::GetVersionFromString(external_version));
139      if (!version.get()) {
140        LOG(WARNING) << "Malformed extension dictionary for extension: "
141                     << extension_id.c_str() << ".  Invalid version string \""
142                     << external_version << "\".";
143        continue;
144      }
145      service_->OnExternalExtensionFileFound(extension_id, version.get(), path,
146                                             crx_location_);
147    } else { // if (has_external_update_url)
148      CHECK(has_external_update_url);  // Checking of keys above ensures this.
149      if (download_location_ == Extension::INVALID) {
150        LOG(WARNING) << "This provider does not support installing external "
151                     << "extensions from update URLs.";
152        continue;
153      }
154      GURL update_url(external_update_url);
155      if (!update_url.is_valid()) {
156        LOG(WARNING) << "Malformed extension dictionary for extension: "
157                     << extension_id.c_str() << ".  Key " << kExternalUpdateUrl
158                     << " has value \"" << external_update_url
159                     << "\", which is not a valid URL.";
160        continue;
161      }
162      service_->OnExternalExtensionUpdateUrlFound(
163          extension_id, update_url, download_location_);
164    }
165  }
166
167  service_->OnExternalProviderReady();
168}
169
170void ExternalExtensionProviderImpl::ServiceShutdown() {
171  service_ = NULL;
172}
173
174bool ExternalExtensionProviderImpl::IsReady() {
175  return ready_;
176}
177
178bool ExternalExtensionProviderImpl::HasExtension(
179    const std::string& id) const {
180  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
181  CHECK(prefs_.get());
182  CHECK(ready_);
183  return prefs_->HasKey(id);
184}
185
186bool ExternalExtensionProviderImpl::GetExtensionDetails(
187    const std::string& id, Extension::Location* location,
188    scoped_ptr<Version>* version) const {
189  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190  CHECK(prefs_.get());
191  CHECK(ready_);
192  DictionaryValue* extension = NULL;
193  if (!prefs_->GetDictionary(id, &extension))
194    return false;
195
196  Extension::Location loc = Extension::INVALID;
197  if (extension->HasKey(kExternalUpdateUrl)) {
198    loc = download_location_;
199
200  } else if (extension->HasKey(kExternalCrx)) {
201    loc = crx_location_;
202
203    std::string external_version;
204    if (!extension->GetString(kExternalVersion, &external_version))
205      return false;
206
207    if (version)
208      version->reset(Version::GetVersionFromString(external_version));
209
210  } else {
211    NOTREACHED();  // Chrome should not allow prefs to get into this state.
212    return false;
213  }
214
215  if (location)
216    *location = loc;
217
218  return true;
219}
220
221// static
222void ExternalExtensionProviderImpl::CreateExternalProviders(
223    VisitorInterface* service,
224    Profile* profile,
225    ProviderCollection* provider_list) {
226  provider_list->push_back(
227      linked_ptr<ExternalExtensionProviderInterface>(
228          new ExternalExtensionProviderImpl(
229              service,
230              new ExternalPrefExtensionLoader(
231                  app::DIR_EXTERNAL_EXTENSIONS),
232              Extension::EXTERNAL_PREF,
233              Extension::EXTERNAL_PREF_DOWNLOAD)));
234
235#if defined(OS_CHROMEOS)
236  // Chrome OS specific source for OEM customization.
237  provider_list->push_back(
238      linked_ptr<ExternalExtensionProviderInterface>(
239          new ExternalExtensionProviderImpl(
240              service,
241              new ExternalPrefExtensionLoader(
242                  chrome::DIR_USER_EXTERNAL_EXTENSIONS),
243              Extension::EXTERNAL_PREF,
244              Extension::EXTERNAL_PREF_DOWNLOAD)));
245#endif
246#if defined(OS_WIN)
247  provider_list->push_back(
248      linked_ptr<ExternalExtensionProviderInterface>(
249          new ExternalExtensionProviderImpl(
250              service,
251              new ExternalRegistryExtensionLoader,
252              Extension::EXTERNAL_REGISTRY,
253              Extension::INVALID)));
254#endif
255  provider_list->push_back(
256      linked_ptr<ExternalExtensionProviderInterface>(
257          new ExternalExtensionProviderImpl(
258              service,
259              new ExternalPolicyExtensionLoader(profile),
260              Extension::INVALID,
261              Extension::EXTERNAL_POLICY_DOWNLOAD)));
262}
263