blob: 38fb70377f9c74cb7dc950ee94e1c13c0d44a8b9 [file] [log] [blame]
[email protected]6014d672008-12-05 00:38:251// Copyright (c) 2006-2008 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/extensions_service.h"
6
7#include "base/file_util.h"
[email protected]cc655912009-01-29 23:19:198#include "base/scoped_handle.h"
9#include "base/scoped_temp_dir.h"
[email protected]6014d672008-12-05 00:38:2510#include "base/string_util.h"
[email protected]cc655912009-01-29 23:19:1911#include "base/third_party/nss/blapi.h"
12#include "base/third_party/nss/sha256.h"
[email protected]6014d672008-12-05 00:38:2513#include "base/thread.h"
[email protected]cc655912009-01-29 23:19:1914#include "base/values.h"
15#include "net/base/file_stream.h"
[email protected]6014d672008-12-05 00:38:2516#include "chrome/browser/browser_process.h"
[email protected]69f1be82009-04-16 22:27:2117#include "chrome/browser/browsing_instance.h"
18#include "chrome/browser/extensions/extension.h"
[email protected]b68d5ed2009-04-16 02:41:2819#include "chrome/browser/extensions/extension_browser_event_router.h"
[email protected]bb28e062009-02-27 17:19:1820#include "chrome/browser/extensions/extension_error_reporter.h"
[email protected]c64631652009-04-29 22:24:3121#include "chrome/browser/extensions/extension_host.h"
[email protected]81e63782009-02-27 19:35:0922#include "chrome/browser/extensions/extension_view.h"
[email protected]c64631652009-04-29 22:24:3123#include "chrome/browser/extensions/user_script_master.h"
[email protected]367230c52009-02-21 01:44:3024#include "chrome/browser/plugin_service.h"
[email protected]81e63782009-02-27 19:35:0925#include "chrome/browser/profile.h"
[email protected]69f1be82009-04-16 22:27:2126#include "chrome/browser/tab_contents/site_instance.h"
[email protected]6014d672008-12-05 00:38:2527#include "chrome/common/json_value_serializer.h"
[email protected]82891262008-12-24 00:21:2628#include "chrome/common/notification_service.h"
[email protected]cc655912009-01-29 23:19:1929#include "chrome/common/unzip.h"
[email protected]b0beaa662009-02-26 00:04:1530
[email protected]c64631652009-04-29 22:24:3131#include "chrome/browser/browser_list.h"
32
[email protected]79db6232009-02-13 20:51:2033#if defined(OS_WIN)
[email protected]b0beaa662009-02-26 00:04:1534#include "base/registry.h"
[email protected]79db6232009-02-13 20:51:2035#endif
[email protected]6014d672008-12-05 00:38:2536
37// ExtensionsService
38
[email protected]cc655912009-01-29 23:19:1939const char* ExtensionsService::kInstallDirectoryName = "Extensions";
40const char* ExtensionsService::kCurrentVersionFileName = "Current Version";
41const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL";
[email protected]b0beaa662009-02-26 00:04:1542
43namespace {
[email protected]cc655912009-01-29 23:19:1944// Chromium Extension magic number
[email protected]b0beaa662009-02-26 00:04:1545const char kExtensionFileMagic[] = "Cr24";
[email protected]cc655912009-01-29 23:19:1946
47struct ExtensionHeader {
48 char magic[sizeof(kExtensionFileMagic) - 1];
49 uint32 version;
50 size_t header_size;
51 size_t manifest_size;
52};
53
54const size_t kZipHashBytes = 32; // SHA-256
55const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size.
[email protected]6014d672008-12-05 00:38:2556
[email protected]b0beaa662009-02-26 00:04:1557#if defined(OS_WIN)
58
59// Registry key where registry defined extension installers live.
60const wchar_t kRegistryExtensions[] = L"Software\\Google\\Chrome\\Extensions";
61
62// Registry value of of that key that defines the path to the .crx file.
63const wchar_t kRegistryExtensionPath[] = L"path";
64
65// Registry value of that key that defines the current version of the .crx file.
66const wchar_t kRegistryExtensionVersion[] = L"version";
67
68#endif
69
70// A marker file to indicate that an extension was installed from an external
71// source.
72const char kExternalInstallFile[] = "EXTERNAL_INSTALL";
[email protected]0b344962009-03-31 04:21:4573
74// The version of the extension package that this code understands.
75const uint32 kExpectedVersion = 1;
[email protected]b0beaa662009-02-26 00:04:1576}
77
[email protected]81e63782009-02-27 19:35:0978ExtensionsService::ExtensionsService(Profile* profile,
[email protected]bdbc87c2009-01-25 05:08:5479 UserScriptMaster* user_script_master)
[email protected]6014d672008-12-05 00:38:2580 : message_loop_(MessageLoop::current()),
[email protected]81e63782009-02-27 19:35:0981 install_directory_(profile->GetPath().AppendASCII(kInstallDirectoryName)),
[email protected]cc5da332009-03-04 08:02:5182 backend_(new ExtensionsServiceBackend(install_directory_)),
[email protected]69f1be82009-04-16 22:27:2183 user_script_master_(user_script_master),
84 browsing_instance_(new BrowsingInstance(profile)) {
[email protected]6014d672008-12-05 00:38:2585}
86
87ExtensionsService::~ExtensionsService() {
[email protected]c64631652009-04-29 22:24:3188 for (ExtensionHostList::iterator iter = background_hosts_.begin();
89 iter != background_hosts_.end(); ++iter) {
90 delete *iter;
91 }
92
[email protected]6014d672008-12-05 00:38:2593 for (ExtensionList::iterator iter = extensions_.begin();
94 iter != extensions_.end(); ++iter) {
95 delete *iter;
96 }
97}
98
99bool ExtensionsService::Init() {
[email protected]b68d5ed2009-04-16 02:41:28100 // Start up the extension event routers.
101 ExtensionBrowserEventRouter::GetInstance()->Init();
102
[email protected]b0beaa662009-02-26 00:04:15103#if defined(OS_WIN)
[email protected]b0beaa662009-02-26 00:04:15104 // TODO(port): ExtensionsServiceBackend::CheckForExternalUpdates depends on
105 // the Windows registry.
106 // TODO(erikkay): Should we monitor the registry during run as well?
107 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
108 NewRunnableMethod(backend_.get(),
109 &ExtensionsServiceBackend::CheckForExternalUpdates,
[email protected]b0beaa662009-02-26 00:04:15110 scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
111#endif
112
[email protected]6014d672008-12-05 00:38:25113 // TODO(aa): This message loop should probably come from a backend
114 // interface, similar to how the message loop for the frontend comes
115 // from the frontend interface.
116 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
117 NewRunnableMethod(backend_.get(),
[email protected]cc5da332009-03-04 08:02:51118 &ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory,
[email protected]6014d672008-12-05 00:38:25119 scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
[email protected]6014d672008-12-05 00:38:25120
121 return true;
122}
123
[email protected]6014d672008-12-05 00:38:25124MessageLoop* ExtensionsService::GetMessageLoop() {
125 return message_loop_;
126}
127
[email protected]3cf4f0992009-02-03 23:00:30128void ExtensionsService::InstallExtension(const FilePath& extension_path) {
129 // TODO(aa): This message loop should probably come from a backend
130 // interface, similar to how the message loop for the frontend comes
131 // from the frontend interface.
132 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
133 NewRunnableMethod(backend_.get(),
134 &ExtensionsServiceBackend::InstallExtension,
135 extension_path,
[email protected]3cf4f0992009-02-03 23:00:30136 scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
137}
138
139void ExtensionsService::LoadExtension(const FilePath& extension_path) {
140 // TODO(aa): This message loop should probably come from a backend
141 // interface, similar to how the message loop for the frontend comes
142 // from the frontend interface.
143 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
144 NewRunnableMethod(backend_.get(),
145 &ExtensionsServiceBackend::LoadSingleExtension,
146 extension_path,
147 scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
148}
149
[email protected]4a8d3272009-03-10 19:15:08150void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) {
[email protected]08816d0d2008-12-08 18:43:53151 extensions_.insert(extensions_.end(), new_extensions->begin(),
152 new_extensions->end());
[email protected]6014d672008-12-05 00:38:25153
[email protected]367230c52009-02-21 01:44:30154 // TODO: Fix race here. A page could need a user script on startup, before
155 // the user script is loaded. We need to freeze the renderer in that case.
156 // TODO(mpcomplete): We also need to force a renderer to refresh its cache of
157 // the plugin list when we inject user scripts, since it could have a stale
158 // version by the time extensions are loaded.
159
[email protected]bdbc87c2009-01-25 05:08:54160 for (ExtensionList::iterator extension = extensions_.begin();
161 extension != extensions_.end(); ++extension) {
[email protected]367230c52009-02-21 01:44:30162 // Tell NPAPI about any plugins in the loaded extensions.
163 if (!(*extension)->plugins_dir().empty()) {
164 PluginService::GetInstance()->AddExtraPluginDir(
165 (*extension)->plugins_dir());
166 }
167
168 // Tell UserScriptMaster about any scripts in the loaded extensions.
[email protected]34aa8dc2009-02-19 07:03:05169 const UserScriptList& scripts = (*extension)->content_scripts();
[email protected]bdbc87c2009-01-25 05:08:54170 for (UserScriptList::const_iterator script = scripts.begin();
171 script != scripts.end(); ++script) {
172 user_script_master_->AddLoneScript(*script);
173 }
[email protected]c64631652009-04-29 22:24:31174
175 // Start the process for the master page, if it exists.
176 if ((*extension)->background_url().is_valid()) {
177 CreateBackgroundHost(*extension, (*extension)->background_url());
178 }
[email protected]bdbc87c2009-01-25 05:08:54179 }
180
[email protected]cc5da332009-03-04 08:02:51181 // Since user scripts may have changed, tell UserScriptMaster to kick off
182 // a scan.
[email protected]bdbc87c2009-01-25 05:08:54183 user_script_master_->StartScan();
184
[email protected]bfd04a62009-02-01 18:16:56185 NotificationService::current()->Notify(
186 NotificationType::EXTENSIONS_LOADED,
[email protected]82891262008-12-24 00:21:26187 NotificationService::AllSources(),
188 Details<ExtensionList>(new_extensions));
189
190 delete new_extensions;
[email protected]6014d672008-12-05 00:38:25191}
192
[email protected]b0beaa662009-02-26 00:04:15193void ExtensionsService::OnExtensionInstalled(FilePath path, bool update) {
[email protected]bfd04a62009-02-01 18:16:56194 NotificationService::current()->Notify(
195 NotificationType::EXTENSION_INSTALLED,
[email protected]cc655912009-01-29 23:19:19196 NotificationService::AllSources(),
197 Details<FilePath>(&path));
198
[email protected]b0beaa662009-02-26 00:04:15199 // TODO(erikkay): Update UI if appropriate.
[email protected]cc655912009-01-29 23:19:19200}
201
[email protected]69f1be82009-04-16 22:27:21202ExtensionView* ExtensionsService::CreateView(Extension* extension,
203 const GURL& url,
204 Browser* browser) {
[email protected]c64631652009-04-29 22:24:31205 return new ExtensionView(
206 new ExtensionHost(extension, GetSiteInstanceForURL(url)), browser, url);
207}
208
209void ExtensionsService::CreateBackgroundHost(Extension* extension,
210 const GURL& url) {
211 ExtensionHost* host =
212 new ExtensionHost(extension, GetSiteInstanceForURL(url));
213 host->CreateRenderView(url, NULL); // create a RenderViewHost with no view
214 background_hosts_.push_back(host);
[email protected]69f1be82009-04-16 22:27:21215}
216
217SiteInstance* ExtensionsService::GetSiteInstanceForURL(const GURL& url) {
218 return browsing_instance_->GetSiteInstanceForURL(url);
219}
220
[email protected]6014d672008-12-05 00:38:25221
222// ExtensionsServicesBackend
223
[email protected]cc5da332009-03-04 08:02:51224void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory(
[email protected]6014d672008-12-05 00:38:25225 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
[email protected]b0beaa662009-02-26 00:04:15226 frontend_ = frontend;
227 alert_on_error_ = false;
[email protected]05eb0fa2009-02-21 00:05:48228
[email protected]540f91b2009-03-26 19:37:43229#if defined(OS_WIN)
230 // On POSIX, AbsolutePath() calls realpath() which returns NULL if
231 // it does not exist. Instead we absolute-ify after creation in
232 // case that is needed.
233 // TODO(port): does this really need to happen before
234 // CreateDirectory() on Windows?
[email protected]cc5da332009-03-04 08:02:51235 if (!file_util::AbsolutePath(&install_directory_))
[email protected]eab9b452009-01-23 20:48:59236 NOTREACHED();
[email protected]540f91b2009-03-26 19:37:43237#endif
[email protected]eab9b452009-01-23 20:48:59238
[email protected]b0beaa662009-02-26 00:04:15239 scoped_ptr<ExtensionList> extensions(new ExtensionList);
240
241 // Create the <Profile>/Extensions directory if it doesn't exist.
[email protected]cc5da332009-03-04 08:02:51242 if (!file_util::DirectoryExists(install_directory_)) {
243 file_util::CreateDirectory(install_directory_);
[email protected]b0beaa662009-02-26 00:04:15244 LOG(INFO) << "Created Extensions directory. No extensions to install.";
245 ReportExtensionsLoaded(extensions.release());
246 return;
247 }
248
[email protected]540f91b2009-03-26 19:37:43249#if !defined(OS_WIN)
250 if (!file_util::AbsolutePath(&install_directory_))
251 NOTREACHED();
252#endif
253
[email protected]bf24d2c2009-02-24 23:07:45254 LOG(INFO) << "Loading installed extensions...";
255
[email protected]6014d672008-12-05 00:38:25256 // Find all child directories in the install directory and load their
257 // manifests. Post errors and results to the frontend.
[email protected]cc5da332009-03-04 08:02:51258 file_util::FileEnumerator enumerator(install_directory_,
[email protected]cc655912009-01-29 23:19:19259 false, // not recursive
[email protected]6014d672008-12-05 00:38:25260 file_util::FileEnumerator::DIRECTORIES);
[email protected]cc5da332009-03-04 08:02:51261 FilePath extension_path;
262 for (extension_path = enumerator.Next(); !extension_path.value().empty();
263 extension_path = enumerator.Next()) {
264 std::string extension_id = WideToASCII(
265 extension_path.BaseName().ToWStringHack());
266 if (CheckExternalUninstall(extension_path, extension_id)) {
267 // TODO(erikkay): Possibly defer this operation to avoid slowing initial
268 // load of extensions.
269 UninstallExtension(extension_path);
270
271 // No error needs to be reported. The extension effectively doesn't
272 // exist.
273 continue;
274 }
275
276 Extension* extension = LoadExtensionCurrentVersion(extension_path);
[email protected]0877fd92009-02-03 16:34:06277 if (extension)
278 extensions->push_back(extension);
[email protected]6014d672008-12-05 00:38:25279 }
280
[email protected]bf24d2c2009-02-24 23:07:45281 LOG(INFO) << "Done.";
[email protected]b0beaa662009-02-26 00:04:15282 ReportExtensionsLoaded(extensions.release());
[email protected]6014d672008-12-05 00:38:25283}
284
[email protected]b0beaa662009-02-26 00:04:15285void ExtensionsServiceBackend::LoadSingleExtension(
[email protected]0877fd92009-02-03 16:34:06286 const FilePath& path_in,
287 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
[email protected]b0beaa662009-02-26 00:04:15288 frontend_ = frontend;
289
290 // Explicit UI loads are always noisy.
291 alert_on_error_ = true;
292
[email protected]cc5da332009-03-04 08:02:51293 FilePath extension_path = path_in;
294 if (!file_util::AbsolutePath(&extension_path))
[email protected]0877fd92009-02-03 16:34:06295 NOTREACHED();
[email protected]bf24d2c2009-02-24 23:07:45296
297 LOG(INFO) << "Loading single extension from " <<
[email protected]cc5da332009-03-04 08:02:51298 WideToASCII(extension_path.BaseName().ToWStringHack());
[email protected]bf24d2c2009-02-24 23:07:45299
[email protected]5bfb1eb0a2009-04-08 18:33:30300 Extension* extension = LoadExtension(extension_path,
301 false); // don't require ID
[email protected]0877fd92009-02-03 16:34:06302 if (extension) {
303 ExtensionList* extensions = new ExtensionList;
304 extensions->push_back(extension);
[email protected]b0beaa662009-02-26 00:04:15305 ReportExtensionsLoaded(extensions);
[email protected]0877fd92009-02-03 16:34:06306 }
[email protected]0877fd92009-02-03 16:34:06307}
308
[email protected]cc5da332009-03-04 08:02:51309Extension* ExtensionsServiceBackend::LoadExtensionCurrentVersion(
310 const FilePath& extension_path) {
[email protected]b0beaa662009-02-26 00:04:15311 std::string version_str;
[email protected]cc5da332009-03-04 08:02:51312 if (!ReadCurrentVersion(extension_path, &version_str)) {
313 ReportExtensionLoadError(extension_path,
314 StringPrintf("Could not read '%s' file.",
315 ExtensionsService::kCurrentVersionFileName));
[email protected]b0beaa662009-02-26 00:04:15316 return NULL;
317 }
318
319 LOG(INFO) << " " <<
[email protected]cc5da332009-03-04 08:02:51320 WideToASCII(extension_path.BaseName().ToWStringHack()) <<
[email protected]b0beaa662009-02-26 00:04:15321 " version: " << version_str;
322
[email protected]5bfb1eb0a2009-04-08 18:33:30323 return LoadExtension(extension_path.AppendASCII(version_str),
324 true); // require id
[email protected]b0beaa662009-02-26 00:04:15325}
326
[email protected]cc5da332009-03-04 08:02:51327Extension* ExtensionsServiceBackend::LoadExtension(
[email protected]5bfb1eb0a2009-04-08 18:33:30328 const FilePath& extension_path, bool require_id) {
[email protected]0877fd92009-02-03 16:34:06329 FilePath manifest_path =
[email protected]cc5da332009-03-04 08:02:51330 extension_path.AppendASCII(Extension::kManifestFilename);
[email protected]0877fd92009-02-03 16:34:06331 if (!file_util::PathExists(manifest_path)) {
[email protected]cc5da332009-03-04 08:02:51332 ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError);
[email protected]0877fd92009-02-03 16:34:06333 return NULL;
334 }
335
[email protected]1635bc12009-04-03 17:18:27336 JSONFileValueSerializer serializer(manifest_path);
[email protected]0877fd92009-02-03 16:34:06337 std::string error;
338 scoped_ptr<Value> root(serializer.Deserialize(&error));
339 if (!root.get()) {
[email protected]cc5da332009-03-04 08:02:51340 ReportExtensionLoadError(extension_path, error);
[email protected]0877fd92009-02-03 16:34:06341 return NULL;
342 }
343
344 if (!root->IsType(Value::TYPE_DICTIONARY)) {
[email protected]cc5da332009-03-04 08:02:51345 ReportExtensionLoadError(extension_path, Extension::kInvalidManifestError);
[email protected]0877fd92009-02-03 16:34:06346 return NULL;
347 }
348
[email protected]cc5da332009-03-04 08:02:51349 scoped_ptr<Extension> extension(new Extension(extension_path));
[email protected]0877fd92009-02-03 16:34:06350 if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()),
[email protected]5bfb1eb0a2009-04-08 18:33:30351 require_id, &error)) {
[email protected]cc5da332009-03-04 08:02:51352 ReportExtensionLoadError(extension_path, error);
[email protected]0877fd92009-02-03 16:34:06353 return NULL;
354 }
[email protected]8d6d9ff2009-02-20 08:14:39355
356 // Validate that claimed resources actually exist.
[email protected]3cfbd0e2009-03-18 21:26:24357 for (size_t i = 0; i < extension->content_scripts().size(); ++i) {
358 const UserScript& script = extension->content_scripts()[i];
359
360 for (size_t j = 0; j < script.js_scripts().size(); j++) {
361 const FilePath& path = script.js_scripts()[j].path();
362 if (!file_util::PathExists(path)) {
363 ReportExtensionLoadError(extension_path,
364 StringPrintf("Could not load '%s' for content script.",
365 WideToUTF8(path.ToWStringHack()).c_str()));
366 return NULL;
367 }
368 }
369
370 for (size_t j = 0; j < script.css_scripts().size(); j++) {
371 const FilePath& path = script.css_scripts()[j].path();
372 if (!file_util::PathExists(path)) {
373 ReportExtensionLoadError(extension_path,
374 StringPrintf("Could not load '%s' for content script.",
375 WideToUTF8(path.ToWStringHack()).c_str()));
376 return NULL;
377 }
[email protected]8d6d9ff2009-02-20 08:14:39378 }
379 }
380
[email protected]0877fd92009-02-03 16:34:06381 return extension.release();
382}
383
[email protected]6014d672008-12-05 00:38:25384void ExtensionsServiceBackend::ReportExtensionLoadError(
[email protected]cc5da332009-03-04 08:02:51385 const FilePath& extension_path, const std::string &error) {
[email protected]b0beaa662009-02-26 00:04:15386 // TODO(port): note that this isn't guaranteed to work properly on Linux.
[email protected]cc5da332009-03-04 08:02:51387 std::string path_str = WideToASCII(extension_path.ToWStringHack());
[email protected]3acbd422008-12-08 18:25:00388 std::string message = StringPrintf("Could not load extension from '%s'. %s",
[email protected]cc655912009-01-29 23:19:19389 path_str.c_str(), error.c_str());
[email protected]bb28e062009-02-27 17:19:18390 ExtensionErrorReporter::GetInstance()->ReportError(message, alert_on_error_);
[email protected]6014d672008-12-05 00:38:25391}
392
393void ExtensionsServiceBackend::ReportExtensionsLoaded(
[email protected]b0beaa662009-02-26 00:04:15394 ExtensionList* extensions) {
395 frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
396 frontend_,
[email protected]cc5da332009-03-04 08:02:51397 &ExtensionsServiceFrontendInterface::OnExtensionsLoaded,
[email protected]6014d672008-12-05 00:38:25398 extensions));
399}
[email protected]cc655912009-01-29 23:19:19400
401// The extension file format is a header, followed by the manifest, followed
402// by the zip file. The header is a magic number, a version, the size of the
403// header, and the size of the manifest. These ints are 4 byte little endian.
[email protected]cc5da332009-03-04 08:02:51404DictionaryValue* ExtensionsServiceBackend::ReadManifest(
405 const FilePath& extension_path) {
406 ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb"));
[email protected]cc655912009-01-29 23:19:19407 if (!file.get()) {
[email protected]cc5da332009-03-04 08:02:51408 ReportExtensionInstallError(extension_path, "no such extension file");
[email protected]cc655912009-01-29 23:19:19409 return NULL;
410 }
411
412 // Read and verify the header.
413 ExtensionHeader header;
414 size_t len;
[email protected]b0beaa662009-02-26 00:04:15415
[email protected]cc655912009-01-29 23:19:19416 // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it
417 // appears that we don't have any endian/alignment aware serialization
418 // code in the code base. So for now, this assumes that we're running
419 // on a little endian machine with 4 byte alignment.
420 len = fread(&header, 1, sizeof(ExtensionHeader), file.get());
421 if (len < sizeof(ExtensionHeader)) {
[email protected]cc5da332009-03-04 08:02:51422 ReportExtensionInstallError(extension_path, "invalid extension header");
[email protected]cc655912009-01-29 23:19:19423 return NULL;
424 }
425 if (strncmp(kExtensionFileMagic, header.magic, sizeof(header.magic))) {
[email protected]cc5da332009-03-04 08:02:51426 ReportExtensionInstallError(extension_path, "bad magic number");
[email protected]cc655912009-01-29 23:19:19427 return NULL;
428 }
[email protected]0b344962009-03-31 04:21:45429 if (header.version != kExpectedVersion) {
[email protected]cc5da332009-03-04 08:02:51430 ReportExtensionInstallError(extension_path, "bad version number");
[email protected]cc655912009-01-29 23:19:19431 return NULL;
432 }
433 if (header.header_size > sizeof(ExtensionHeader))
434 fseek(file.get(), header.header_size - sizeof(ExtensionHeader), SEEK_CUR);
[email protected]f0a51fb52009-03-05 12:46:38435
[email protected]cc655912009-01-29 23:19:19436 char buf[1 << 16];
437 std::string manifest_str;
438 size_t read_size = std::min(sizeof(buf), header.manifest_size);
439 size_t remainder = header.manifest_size;
440 while ((len = fread(buf, 1, read_size, file.get())) > 0) {
441 manifest_str.append(buf, len);
442 if (len <= remainder)
443 break;
444 remainder -= len;
445 read_size = std::min(sizeof(buf), remainder);
446 }
447
448 // Verify the JSON
449 JSONStringValueSerializer json(manifest_str);
450 std::string error;
[email protected]4c7ca4b2009-02-04 00:53:08451 scoped_ptr<Value> val(json.Deserialize(&error));
452 if (!val.get()) {
[email protected]cc5da332009-03-04 08:02:51453 ReportExtensionInstallError(extension_path, error);
[email protected]cc655912009-01-29 23:19:19454 return NULL;
455 }
456 if (!val->IsType(Value::TYPE_DICTIONARY)) {
[email protected]cc5da332009-03-04 08:02:51457 ReportExtensionInstallError(extension_path,
458 "manifest isn't a JSON dictionary");
[email protected]cc655912009-01-29 23:19:19459 return NULL;
460 }
[email protected]4c7ca4b2009-02-04 00:53:08461 DictionaryValue* manifest = static_cast<DictionaryValue*>(val.get());
[email protected]b0beaa662009-02-26 00:04:15462
463 // Check the version before proceeding. Although we verify the version
464 // again later, checking it here allows us to skip some potentially expensive
465 // work.
466 std::string id;
[email protected]8e50b602009-03-03 22:59:43467 if (!manifest->GetString(Extension::kIdKey, &id)) {
[email protected]cc5da332009-03-04 08:02:51468 ReportExtensionInstallError(extension_path, "missing id key");
[email protected]b0beaa662009-02-26 00:04:15469 return NULL;
470 }
471 FilePath dest_dir = install_directory_.AppendASCII(id.c_str());
472 if (file_util::PathExists(dest_dir)) {
473 std::string version;
[email protected]8e50b602009-03-03 22:59:43474 if (!manifest->GetString(Extension::kVersionKey, &version)) {
[email protected]cc5da332009-03-04 08:02:51475 ReportExtensionInstallError(extension_path, "missing version key");
[email protected]b0beaa662009-02-26 00:04:15476 return NULL;
477 }
478 std::string current_version;
479 if (ReadCurrentVersion(dest_dir, &current_version)) {
480 if (!CheckCurrentVersion(version, current_version, dest_dir))
481 return NULL;
482 }
483 }
484
[email protected]cc655912009-01-29 23:19:19485 std::string zip_hash;
[email protected]8e50b602009-03-03 22:59:43486 if (!manifest->GetString(Extension::kZipHashKey, &zip_hash)) {
[email protected]cc5da332009-03-04 08:02:51487 ReportExtensionInstallError(extension_path, "missing zip_hash key");
[email protected]cc655912009-01-29 23:19:19488 return NULL;
489 }
490 if (zip_hash.size() != kZipHashHexBytes) {
[email protected]cc5da332009-03-04 08:02:51491 ReportExtensionInstallError(extension_path, "invalid zip_hash key");
[email protected]cc655912009-01-29 23:19:19492 return NULL;
493 }
494
495 // Read the rest of the zip file and compute a hash to compare against
496 // what the manifest claims. Compute the hash incrementally since the
497 // zip file could be large.
498 const unsigned char* ubuf = reinterpret_cast<const unsigned char*>(buf);
499 SHA256Context ctx;
500 SHA256_Begin(&ctx);
501 while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0)
502 SHA256_Update(&ctx, ubuf, len);
503 uint8 hash[32];
504 SHA256_End(&ctx, hash, NULL, sizeof(hash));
505
506 std::vector<uint8> zip_hash_bytes;
507 if (!HexStringToBytes(zip_hash, &zip_hash_bytes)) {
[email protected]cc5da332009-03-04 08:02:51508 ReportExtensionInstallError(extension_path, "invalid zip_hash key");
[email protected]cc655912009-01-29 23:19:19509 return NULL;
510 }
511 if (zip_hash_bytes.size() != kZipHashBytes) {
[email protected]cc5da332009-03-04 08:02:51512 ReportExtensionInstallError(extension_path, "invalid zip_hash key");
[email protected]cc655912009-01-29 23:19:19513 return NULL;
514 }
515 for (size_t i = 0; i < kZipHashBytes; ++i) {
516 if (zip_hash_bytes[i] != hash[i]) {
[email protected]cc5da332009-03-04 08:02:51517 ReportExtensionInstallError(extension_path,
518 "zip_hash key didn't match zip hash");
[email protected]cc655912009-01-29 23:19:19519 return NULL;
520 }
521 }
522
523 // TODO(erikkay): The manifest will also contain a signature of the hash
524 // (or perhaps the whole manifest) for authentication purposes.
525
[email protected]4c7ca4b2009-02-04 00:53:08526 // The caller owns val (now cast to manifest).
527 val.release();
[email protected]cc655912009-01-29 23:19:19528 return manifest;
529}
530
[email protected]b0beaa662009-02-26 00:04:15531bool ExtensionsServiceBackend::ReadCurrentVersion(const FilePath& dir,
532 std::string* version_string) {
[email protected]18a12352009-01-31 01:33:28533 FilePath current_version =
[email protected]b0beaa662009-02-26 00:04:15534 dir.AppendASCII(ExtensionsService::kCurrentVersionFileName);
[email protected]18a12352009-01-31 01:33:28535 if (file_util::PathExists(current_version)) {
536 if (file_util::ReadFileToString(current_version, version_string)) {
537 TrimWhitespace(*version_string, TRIM_ALL, version_string);
538 return true;
539 }
540 }
541 return false;
542}
543
[email protected]cc655912009-01-29 23:19:19544bool ExtensionsServiceBackend::CheckCurrentVersion(
[email protected]b0beaa662009-02-26 00:04:15545 const std::string& new_version_str,
546 const std::string& current_version_str,
547 const FilePath& dest_dir) {
548 scoped_ptr<Version> current_version(
549 Version::GetVersionFromString(current_version_str));
550 scoped_ptr<Version> new_version(
551 Version::GetVersionFromString(new_version_str));
552 if (current_version->CompareTo(*new_version) >= 0) {
[email protected]b0beaa662009-02-26 00:04:15553 // Verify that the directory actually exists. If it doesn't we'll return
554 // true so that the install code will repair the broken installation.
555 // TODO(erikkay): A further step would be to verify that the extension
556 // has actually loaded successfully.
557 FilePath version_dir = dest_dir.AppendASCII(current_version_str);
558 if (file_util::PathExists(version_dir)) {
[email protected]f0a51fb52009-03-05 12:46:38559 ReportExtensionInstallError(dest_dir,
[email protected]b0beaa662009-02-26 00:04:15560 "Existing version is already up to date.");
561 return false;
[email protected]cc655912009-01-29 23:19:19562 }
563 }
564 return true;
565}
566
[email protected]f0a51fb52009-03-05 12:46:38567bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir,
[email protected]b0beaa662009-02-26 00:04:15568 const FilePath& dest_dir) {
[email protected]cc655912009-01-29 23:19:19569
570 if (file_util::PathExists(dest_dir)) {
[email protected]cc655912009-01-29 23:19:19571 // By the time we get here, it should be safe to assume that this directory
572 // is not currently in use (it's not the current active version).
573 if (!file_util::Delete(dest_dir, true)) {
[email protected]cc5da332009-03-04 08:02:51574 ReportExtensionInstallError(source_dir,
[email protected]cc655912009-01-29 23:19:19575 "Can't delete existing version directory.");
576 return false;
577 }
578 } else {
579 FilePath parent = dest_dir.DirName();
580 if (!file_util::DirectoryExists(parent)) {
581 if (!file_util::CreateDirectory(parent)) {
[email protected]cc5da332009-03-04 08:02:51582 ReportExtensionInstallError(source_dir,
583 "Couldn't create extension directory.");
[email protected]cc655912009-01-29 23:19:19584 return false;
585 }
586 }
587 }
588 if (!file_util::Move(source_dir, dest_dir)) {
[email protected]cc5da332009-03-04 08:02:51589 ReportExtensionInstallError(source_dir,
590 "Couldn't move temporary directory.");
[email protected]cc655912009-01-29 23:19:19591 return false;
592 }
593
594 return true;
595}
596
[email protected]b0beaa662009-02-26 00:04:15597bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir,
598 std::string version) {
[email protected]cc655912009-01-29 23:19:19599 // Write out the new CurrentVersion file.
600 // <profile>/Extension/<name>/CurrentVersion
601 FilePath current_version =
602 dest_dir.AppendASCII(ExtensionsService::kCurrentVersionFileName);
603 FilePath current_version_old =
604 current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old"));
605 if (file_util::PathExists(current_version_old)) {
606 if (!file_util::Delete(current_version_old, false)) {
[email protected]cc5da332009-03-04 08:02:51607 ReportExtensionInstallError(dest_dir,
608 "Couldn't remove CurrentVersion_old file.");
[email protected]cc655912009-01-29 23:19:19609 return false;
610 }
611 }
612 if (file_util::PathExists(current_version)) {
613 if (!file_util::Move(current_version, current_version_old)) {
[email protected]cc5da332009-03-04 08:02:51614 ReportExtensionInstallError(dest_dir,
615 "Couldn't move CurrentVersion file.");
[email protected]cc655912009-01-29 23:19:19616 return false;
617 }
618 }
619 net::FileStream stream;
620 int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE;
621 if (stream.Open(current_version, flags) != 0)
622 return false;
623 if (stream.Write(version.c_str(), version.size(), NULL) < 0) {
[email protected]b0beaa662009-02-26 00:04:15624
[email protected]cc655912009-01-29 23:19:19625 // Restore the old CurrentVersion.
626 if (file_util::PathExists(current_version_old)) {
627 if (!file_util::Move(current_version_old, current_version)) {
[email protected]f0a51fb52009-03-05 12:46:38628 LOG(WARNING) << "couldn't restore " << current_version_old.value() <<
[email protected]cc655912009-01-29 23:19:19629 " to " << current_version.value();
[email protected]b0beaa662009-02-26 00:04:15630
[email protected]cc655912009-01-29 23:19:19631 // TODO(erikkay): This is an ugly state to be in. Try harder?
632 }
633 }
[email protected]f0a51fb52009-03-05 12:46:38634 ReportExtensionInstallError(dest_dir,
[email protected]cc5da332009-03-04 08:02:51635 "Couldn't create CurrentVersion file.");
[email protected]cc655912009-01-29 23:19:19636 return false;
637 }
638 return true;
639}
640
[email protected]b0beaa662009-02-26 00:04:15641void ExtensionsServiceBackend::InstallExtension(
[email protected]cc655912009-01-29 23:19:19642 const FilePath& extension_path,
[email protected]cc655912009-01-29 23:19:19643 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
644 LOG(INFO) << "Installing extension " << extension_path.value();
645
[email protected]b0beaa662009-02-26 00:04:15646 frontend_ = frontend;
[email protected]cc5da332009-03-04 08:02:51647 alert_on_error_ = false;
[email protected]b0beaa662009-02-26 00:04:15648
[email protected]cc5da332009-03-04 08:02:51649 bool was_update = false;
650 FilePath destination_path;
651 if (InstallOrUpdateExtension(extension_path,
652 std::string(), // no expected id
653 &destination_path, &was_update))
654 ReportExtensionInstalled(destination_path.DirName(), was_update);
[email protected]b0beaa662009-02-26 00:04:15655}
656
[email protected]cc5da332009-03-04 08:02:51657bool ExtensionsServiceBackend::InstallOrUpdateExtension(
658 const FilePath& source_file, const std::string& expected_id,
659 FilePath* version_dir, bool* was_update) {
660 if (was_update)
661 *was_update = false;
[email protected]cc655912009-01-29 23:19:19662
663 // Read and verify the extension.
[email protected]cc5da332009-03-04 08:02:51664 scoped_ptr<DictionaryValue> manifest(ReadManifest(source_file));
[email protected]cc655912009-01-29 23:19:19665 if (!manifest.get()) {
[email protected]cc655912009-01-29 23:19:19666 // ReadManifest has already reported the extension error.
[email protected]cc5da332009-03-04 08:02:51667 return false;
[email protected]cc655912009-01-29 23:19:19668 }
669 DictionaryValue* dict = manifest.get();
670 Extension extension;
671 std::string error;
[email protected]5bfb1eb0a2009-04-08 18:33:30672 if (!extension.InitFromValue(*dict,
673 true, // require ID
674 &error)) {
[email protected]cc5da332009-03-04 08:02:51675 ReportExtensionInstallError(source_file,
676 "Invalid extension manifest.");
677 return false;
[email protected]b0beaa662009-02-26 00:04:15678 }
679
[email protected]0b344962009-03-31 04:21:45680 // ID is required for installed extensions.
681 if (extension.id().empty()) {
682 ReportExtensionInstallError(source_file, "Required value 'id' is missing.");
683 return false;
684 }
685
[email protected]b0beaa662009-02-26 00:04:15686 // If an expected id was provided, make sure it matches.
687 if (expected_id.length() && expected_id != extension.id()) {
[email protected]f0a51fb52009-03-05 12:46:38688 ReportExtensionInstallError(source_file,
[email protected]b0beaa662009-02-26 00:04:15689 "ID in new extension manifest does not match expected ID.");
[email protected]cc5da332009-03-04 08:02:51690 return false;
[email protected]cc655912009-01-29 23:19:19691 }
692
693 // <profile>/Extensions/<id>
[email protected]b0beaa662009-02-26 00:04:15694 FilePath dest_dir = install_directory_.AppendASCII(extension.id());
[email protected]cc655912009-01-29 23:19:19695 std::string version = extension.VersionString();
[email protected]b0beaa662009-02-26 00:04:15696 std::string current_version;
697 if (ReadCurrentVersion(dest_dir, &current_version)) {
698 if (!CheckCurrentVersion(version, current_version, dest_dir))
[email protected]cc5da332009-03-04 08:02:51699 return false;
700 if (was_update)
701 *was_update = true;
[email protected]b0beaa662009-02-26 00:04:15702 }
703
704 // <profile>/Extensions/INSTALL_TEMP
705 FilePath temp_dir = install_directory_.AppendASCII(kTempExtensionName);
706
707 // Ensure we're starting with a clean slate.
708 if (file_util::PathExists(temp_dir)) {
709 if (!file_util::Delete(temp_dir, true)) {
[email protected]cc5da332009-03-04 08:02:51710 ReportExtensionInstallError(source_file,
[email protected]b0beaa662009-02-26 00:04:15711 "Couldn't delete existing temporary directory.");
[email protected]cc5da332009-03-04 08:02:51712 return false;
[email protected]b0beaa662009-02-26 00:04:15713 }
714 }
715 ScopedTempDir scoped_temp;
716 scoped_temp.Set(temp_dir);
717 if (!scoped_temp.IsValid()) {
[email protected]f0a51fb52009-03-05 12:46:38718 ReportExtensionInstallError(source_file,
[email protected]cc5da332009-03-04 08:02:51719 "Couldn't create temporary directory.");
720 return false;
[email protected]b0beaa662009-02-26 00:04:15721 }
[email protected]cc655912009-01-29 23:19:19722
723 // <profile>/Extensions/INSTALL_TEMP/<version>
724 FilePath temp_version = temp_dir.AppendASCII(version);
[email protected]b0beaa662009-02-26 00:04:15725 file_util::CreateDirectory(temp_version);
[email protected]cc5da332009-03-04 08:02:51726 if (!Unzip(source_file, temp_version, NULL)) {
727 ReportExtensionInstallError(source_file, "Couldn't unzip extension.");
728 return false;
[email protected]b0beaa662009-02-26 00:04:15729 }
[email protected]cc655912009-01-29 23:19:19730
731 // <profile>/Extensions/<dir_name>/<version>
[email protected]cc5da332009-03-04 08:02:51732 *version_dir = dest_dir.AppendASCII(version);
733 if (!InstallDirSafely(temp_version, *version_dir))
734 return false;
[email protected]cc655912009-01-29 23:19:19735
[email protected]b0beaa662009-02-26 00:04:15736 if (!SetCurrentVersion(dest_dir, version)) {
[email protected]cc5da332009-03-04 08:02:51737 if (!file_util::Delete(*version_dir, true))
[email protected]cc655912009-01-29 23:19:19738 LOG(WARNING) << "Can't remove " << dest_dir.value();
[email protected]cc5da332009-03-04 08:02:51739 return false;
[email protected]cc655912009-01-29 23:19:19740 }
741
[email protected]cc5da332009-03-04 08:02:51742 return true;
[email protected]cc655912009-01-29 23:19:19743}
744
745void ExtensionsServiceBackend::ReportExtensionInstallError(
[email protected]cc5da332009-03-04 08:02:51746 const FilePath& extension_path, const std::string &error) {
[email protected]b0beaa662009-02-26 00:04:15747
[email protected]cc655912009-01-29 23:19:19748 // TODO(erikkay): note that this isn't guaranteed to work properly on Linux.
[email protected]cc5da332009-03-04 08:02:51749 std::string path_str = WideToASCII(extension_path.ToWStringHack());
[email protected]cc655912009-01-29 23:19:19750 std::string message =
751 StringPrintf("Could not install extension from '%s'. %s",
752 path_str.c_str(), error.c_str());
[email protected]bb28e062009-02-27 17:19:18753 ExtensionErrorReporter::GetInstance()->ReportError(message, alert_on_error_);
[email protected]cc655912009-01-29 23:19:19754}
755
756void ExtensionsServiceBackend::ReportExtensionInstalled(
[email protected]cc5da332009-03-04 08:02:51757 const FilePath& path, bool update) {
[email protected]b0beaa662009-02-26 00:04:15758 frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
759 frontend_,
[email protected]cc655912009-01-29 23:19:19760 &ExtensionsServiceFrontendInterface::OnExtensionInstalled,
[email protected]b0beaa662009-02-26 00:04:15761 path,
762 update));
763
764 // After it's installed, load it right away with the same settings.
[email protected]252cc252009-02-26 18:07:50765 LOG(INFO) << "Loading extension " << path.value();
[email protected]cc5da332009-03-04 08:02:51766 Extension* extension = LoadExtensionCurrentVersion(path);
[email protected]252cc252009-02-26 18:07:50767 if (extension) {
768 // Only one extension, but ReportExtensionsLoaded can handle multiple,
769 // so we need to construct a list.
770 scoped_ptr<ExtensionList> extensions(new ExtensionList);
771 extensions->push_back(extension);
772 LOG(INFO) << "Done.";
773 // Hand off ownership of the loaded extensions to the frontend.
774 ReportExtensionsLoaded(extensions.release());
775 }
[email protected]b0beaa662009-02-26 00:04:15776}
777
778// Some extensions will autoupdate themselves externally from Chrome. These
779// are typically part of some larger client application package. To support
780// these, the extension will register its location in the registry on Windows
781// (TODO(port): what about on other platforms?) and this code will periodically
782// check that location for a .crx file, which it will then install locally if
783// a new version is available.
784void ExtensionsServiceBackend::CheckForExternalUpdates(
[email protected]b0beaa662009-02-26 00:04:15785 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
786
787 // Note that this installation is intentionally silent (since it didn't
788 // go through the front-end). Extensions that are registered in this
789 // way are effectively considered 'pre-bundled', and so implicitly
790 // trusted. In general, if something has HKLM or filesystem access,
791 // they could install an extension manually themselves anyway.
792 alert_on_error_ = false;
793 frontend_ = frontend;
[email protected]b0beaa662009-02-26 00:04:15794
795#if defined(OS_WIN)
796 HKEY reg_root = HKEY_LOCAL_MACHINE;
797 RegistryKeyIterator iterator(reg_root, kRegistryExtensions);
798 while (iterator.Valid()) {
799 RegKey key;
800 std::wstring key_path = kRegistryExtensions;
801 key_path.append(L"\\");
802 key_path.append(iterator.Name());
803 if (key.Open(reg_root, key_path.c_str())) {
804 std::wstring extension_path;
805 if (key.ReadValue(kRegistryExtensionPath, &extension_path)) {
806 std::string id = WideToASCII(iterator.Name());
[email protected]b0beaa662009-02-26 00:04:15807 std::wstring extension_version;
808 if (key.ReadValue(kRegistryExtensionVersion, &extension_version)) {
[email protected]cc5da332009-03-04 08:02:51809 if (ShouldInstall(id, WideToASCII(extension_version))) {
810 FilePath version_dir;
811 if (InstallOrUpdateExtension(FilePath(extension_path), id,
812 &version_dir, NULL)) {
813 // To mark that this extension was installed from an external
814 // source, create a zero-length file. At load time, this is used
815 // to indicate that the extension should be uninstalled.
816 // TODO(erikkay): move this into per-extension config storage when
817 // it appears.
818 FilePath marker = version_dir.AppendASCII(
819 kExternalInstallFile);
820 file_util::WriteFile(marker, NULL, 0);
821 }
822 }
[email protected]b0beaa662009-02-26 00:04:15823 } else {
[email protected]b0beaa662009-02-26 00:04:15824 // TODO(erikkay): find a way to get this into about:extensions
825 LOG(WARNING) << "Missing value " << kRegistryExtensionVersion <<
826 " for key " << key_path;
827 }
828 } else {
[email protected]b0beaa662009-02-26 00:04:15829 // TODO(erikkay): find a way to get this into about:extensions
830 LOG(WARNING) << "Missing value " << kRegistryExtensionPath <<
831 " for key " << key_path;
832 }
833 }
834 ++iterator;
835 }
836#else
837 NOTREACHED();
838#endif
839}
840
841bool ExtensionsServiceBackend::CheckExternalUninstall(const FilePath& path,
842 const std::string& id) {
843 FilePath external_file = path.AppendASCII(kExternalInstallFile);
844 if (file_util::PathExists(external_file)) {
845#if defined(OS_WIN)
846 HKEY reg_root = HKEY_LOCAL_MACHINE;
847 RegKey key;
848 std::wstring key_path = kRegistryExtensions;
849 key_path.append(L"\\");
850 key_path.append(ASCIIToWide(id));
851
852 // If the key doesn't exist, then we should uninstall.
853 return !key.Open(reg_root, key_path.c_str());
854#else
855 NOTREACHED();
856#endif
857 }
858 return false;
859}
860
861// Assumes that the extension isn't currently loaded or in use.
862void ExtensionsServiceBackend::UninstallExtension(const FilePath& path) {
863 FilePath parent = path.DirName();
864 FilePath version =
865 parent.AppendASCII(ExtensionsService::kCurrentVersionFileName);
866 bool version_exists = file_util::PathExists(version);
867 DCHECK(version_exists);
868 if (!version_exists) {
869 LOG(WARNING) << "Asked to uninstall bogus extension dir " << parent.value();
870 return;
871 }
872 if (!file_util::Delete(parent, true)) {
873 LOG(WARNING) << "Failed to delete " << parent.value();
874 }
875}
876
877bool ExtensionsServiceBackend::ShouldInstall(const std::string& id,
878 const std::string& version) {
879 FilePath dir(install_directory_.AppendASCII(id.c_str()));
880 std::string current_version;
881 if (ReadCurrentVersion(dir, &current_version)) {
882 return !CheckCurrentVersion(version, current_version, dir);
883 }
884 return true;
[email protected]cc655912009-01-29 23:19:19885}