blob: 78758df1ad2f262e390db2b54a0e3d9d1a960156 [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]bdbc87c2009-01-25 05:08:5417#include "chrome/browser/extensions/user_script_master.h"
[email protected]6014d672008-12-05 00:38:2518#include "chrome/common/json_value_serializer.h"
[email protected]82891262008-12-24 00:21:2619#include "chrome/common/notification_service.h"
[email protected]cc655912009-01-29 23:19:1920#include "chrome/common/unzip.h"
[email protected]6014d672008-12-05 00:38:2521
22// ExtensionsService
23
[email protected]cc655912009-01-29 23:19:1924const char* ExtensionsService::kInstallDirectoryName = "Extensions";
25const char* ExtensionsService::kCurrentVersionFileName = "Current Version";
26const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL";
27// Chromium Extension magic number
28static const char kExtensionFileMagic[] = "Cr24";
29
30struct ExtensionHeader {
31 char magic[sizeof(kExtensionFileMagic) - 1];
32 uint32 version;
33 size_t header_size;
34 size_t manifest_size;
35};
36
37const size_t kZipHashBytes = 32; // SHA-256
38const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size.
[email protected]6014d672008-12-05 00:38:2539
[email protected]bdbc87c2009-01-25 05:08:5440ExtensionsService::ExtensionsService(const FilePath& profile_directory,
41 UserScriptMaster* user_script_master)
[email protected]6014d672008-12-05 00:38:2542 : message_loop_(MessageLoop::current()),
43 backend_(new ExtensionsServiceBackend),
[email protected]cc655912009-01-29 23:19:1944 install_directory_(profile_directory.AppendASCII(kInstallDirectoryName)),
[email protected]bdbc87c2009-01-25 05:08:5445 user_script_master_(user_script_master) {
[email protected]6014d672008-12-05 00:38:2546}
47
48ExtensionsService::~ExtensionsService() {
49 for (ExtensionList::iterator iter = extensions_.begin();
50 iter != extensions_.end(); ++iter) {
51 delete *iter;
52 }
53}
54
55bool ExtensionsService::Init() {
56 // TODO(aa): This message loop should probably come from a backend
57 // interface, similar to how the message loop for the frontend comes
58 // from the frontend interface.
59 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
60 NewRunnableMethod(backend_.get(),
61 &ExtensionsServiceBackend::LoadExtensionsFromDirectory,
62 install_directory_,
63 scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
64 // TODO(aa): Load extensions from other registered directories.
65
66 return true;
67}
68
69MessageLoop* ExtensionsService::GetMessageLoop() {
70 return message_loop_;
71}
72
73void ExtensionsService::OnExtensionsLoadedFromDirectory(
[email protected]08816d0d2008-12-08 18:43:5374 ExtensionList* new_extensions) {
75 extensions_.insert(extensions_.end(), new_extensions->begin(),
76 new_extensions->end());
[email protected]6014d672008-12-05 00:38:2577
[email protected]bdbc87c2009-01-25 05:08:5478 // Tell UserScriptMaster about any scripts in the loaded extensions.
79 for (ExtensionList::iterator extension = extensions_.begin();
80 extension != extensions_.end(); ++extension) {
81 const UserScriptList& scripts = (*extension)->user_scripts();
82 for (UserScriptList::const_iterator script = scripts.begin();
83 script != scripts.end(); ++script) {
84 user_script_master_->AddLoneScript(*script);
85 }
86 }
87
88 // Tell UserScriptMaster to also watch the extensions directory for changes
89 // and then kick off the first scan.
90 // TODO(aa): This should go away when we implement the --extension flag, since
91 // developing scripts in the Extensions directory will no longer be a common
92 // use-case.
93 user_script_master_->AddWatchedPath(install_directory_);
94 user_script_master_->StartScan();
95
[email protected]82891262008-12-24 00:21:2696 NotificationService::current()->Notify(NOTIFY_EXTENSIONS_LOADED,
97 NotificationService::AllSources(),
98 Details<ExtensionList>(new_extensions));
99
100 delete new_extensions;
[email protected]6014d672008-12-05 00:38:25101}
102
[email protected]3acbd422008-12-08 18:25:00103void ExtensionsService::OnExtensionLoadError(const std::string& error) {
104 // TODO(aa): Print the error message out somewhere better. I think we are
105 // going to need some sort of 'extension inspector'.
106 LOG(WARNING) << error;
[email protected]6014d672008-12-05 00:38:25107}
108
[email protected]cc655912009-01-29 23:19:19109void ExtensionsService::InstallExtension(const FilePath& extension_path) {
110 // TODO(aa): This message loop should probably come from a backend
111 // interface, similar to how the message loop for the frontend comes
112 // from the frontend interface.
113 g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
114 NewRunnableMethod(backend_.get(),
115 &ExtensionsServiceBackend::InstallExtension,
116 extension_path,
117 install_directory_,
118 scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
119}
120
121void ExtensionsService::OnExtensionInstallError(const std::string& error) {
122 // TODO(erikkay): Print the error message out somewhere better.
123 LOG(WARNING) << error;
124}
125
126void ExtensionsService::OnExtensionInstalled(FilePath path) {
127 NotificationService::current()->Notify(NOTIFY_EXTENSION_INSTALLED,
128 NotificationService::AllSources(),
129 Details<FilePath>(&path));
130
131 // TODO(erikkay): now what?
132}
133
[email protected]6014d672008-12-05 00:38:25134
135// ExtensionsServicesBackend
136
137bool ExtensionsServiceBackend::LoadExtensionsFromDirectory(
[email protected]eab9b452009-01-23 20:48:59138 const FilePath& path_in,
[email protected]6014d672008-12-05 00:38:25139 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
[email protected]eab9b452009-01-23 20:48:59140 FilePath path = path_in;
141 if (!file_util::AbsolutePath(&path))
142 NOTREACHED();
143
[email protected]6014d672008-12-05 00:38:25144 // Find all child directories in the install directory and load their
145 // manifests. Post errors and results to the frontend.
146 scoped_ptr<ExtensionList> extensions(new ExtensionList);
[email protected]0b733222008-12-11 14:55:12147 file_util::FileEnumerator enumerator(path,
[email protected]cc655912009-01-29 23:19:19148 false, // not recursive
[email protected]6014d672008-12-05 00:38:25149 file_util::FileEnumerator::DIRECTORIES);
[email protected]0b733222008-12-11 14:55:12150 for (FilePath child_path = enumerator.Next(); !child_path.value().empty();
[email protected]6014d672008-12-05 00:38:25151 child_path = enumerator.Next()) {
[email protected]0e292232009-01-22 15:23:34152 FilePath manifest_path =
153 child_path.AppendASCII(Extension::kManifestFilename);
[email protected]6014d672008-12-05 00:38:25154 if (!file_util::PathExists(manifest_path)) {
[email protected]cc655912009-01-29 23:19:19155 ReportExtensionLoadError(frontend.get(), child_path,
[email protected]6014d672008-12-05 00:38:25156 Extension::kInvalidManifestError);
157 continue;
158 }
159
160 JSONFileValueSerializer serializer(manifest_path.ToWStringHack());
[email protected]3acbd422008-12-08 18:25:00161 std::string error;
[email protected]b4cebf82008-12-29 19:59:08162 scoped_ptr<Value> root(serializer.Deserialize(&error));
163 if (!root.get()) {
[email protected]cc655912009-01-29 23:19:19164 ReportExtensionLoadError(frontend.get(), child_path,
[email protected]0b733222008-12-11 14:55:12165 error);
[email protected]6014d672008-12-05 00:38:25166 continue;
167 }
168
[email protected]6014d672008-12-05 00:38:25169 if (!root->IsType(Value::TYPE_DICTIONARY)) {
[email protected]cc655912009-01-29 23:19:19170 ReportExtensionLoadError(frontend.get(), child_path,
[email protected]6014d672008-12-05 00:38:25171 Extension::kInvalidManifestError);
172 continue;
173 }
174
[email protected]82891262008-12-24 00:21:26175 scoped_ptr<Extension> extension(new Extension(child_path));
[email protected]b4cebf82008-12-29 19:59:08176 if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()),
[email protected]6014d672008-12-05 00:38:25177 &error)) {
[email protected]cc655912009-01-29 23:19:19178 ReportExtensionLoadError(frontend.get(), child_path, error);
[email protected]6014d672008-12-05 00:38:25179 continue;
180 }
181
182 extensions->push_back(extension.release());
[email protected]6014d672008-12-05 00:38:25183 }
184
185 ReportExtensionsLoaded(frontend.get(), extensions.release());
186 return true;
187}
188
189void ExtensionsServiceBackend::ReportExtensionLoadError(
[email protected]cc655912009-01-29 23:19:19190 ExtensionsServiceFrontendInterface *frontend, const FilePath& path,
[email protected]3acbd422008-12-08 18:25:00191 const std::string &error) {
[email protected]cc655912009-01-29 23:19:19192 // TODO(erikkay): note that this isn't guaranteed to work properly on Linux.
193 std::string path_str = WideToASCII(path.ToWStringHack());
[email protected]3acbd422008-12-08 18:25:00194 std::string message = StringPrintf("Could not load extension from '%s'. %s",
[email protected]cc655912009-01-29 23:19:19195 path_str.c_str(), error.c_str());
[email protected]6014d672008-12-05 00:38:25196 frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
197 frontend, &ExtensionsServiceFrontendInterface::OnExtensionLoadError,
[email protected]3acbd422008-12-08 18:25:00198 message));
[email protected]6014d672008-12-05 00:38:25199}
200
201void ExtensionsServiceBackend::ReportExtensionsLoaded(
202 ExtensionsServiceFrontendInterface *frontend, ExtensionList* extensions) {
203 frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
204 frontend,
205 &ExtensionsServiceFrontendInterface::OnExtensionsLoadedFromDirectory,
206 extensions));
207}
[email protected]cc655912009-01-29 23:19:19208
209// The extension file format is a header, followed by the manifest, followed
210// by the zip file. The header is a magic number, a version, the size of the
211// header, and the size of the manifest. These ints are 4 byte little endian.
212DictionaryValue* ExtensionsServiceBackend::ReadManifest(
213 const FilePath& extension_path,
214 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
215 ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb"));
216 if (!file.get()) {
217 ReportExtensionInstallError(frontend, extension_path,
218 "no such extension file");
219 return NULL;
220 }
221
222 // Read and verify the header.
223 ExtensionHeader header;
224 size_t len;
225 // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it
226 // appears that we don't have any endian/alignment aware serialization
227 // code in the code base. So for now, this assumes that we're running
228 // on a little endian machine with 4 byte alignment.
229 len = fread(&header, 1, sizeof(ExtensionHeader), file.get());
230 if (len < sizeof(ExtensionHeader)) {
231 ReportExtensionInstallError(frontend, extension_path,
232 "invalid extension header");
233 return NULL;
234 }
235 if (strncmp(kExtensionFileMagic, header.magic, sizeof(header.magic))) {
236 ReportExtensionInstallError(frontend, extension_path,
237 "bad magic number");
238 return NULL;
239 }
240 if (header.version != Extension::kExpectedFormatVersion) {
241 ReportExtensionInstallError(frontend, extension_path,
242 "bad version number");
243 return NULL;
244 }
245 if (header.header_size > sizeof(ExtensionHeader))
246 fseek(file.get(), header.header_size - sizeof(ExtensionHeader), SEEK_CUR);
247
248 char buf[1 << 16];
249 std::string manifest_str;
250 size_t read_size = std::min(sizeof(buf), header.manifest_size);
251 size_t remainder = header.manifest_size;
252 while ((len = fread(buf, 1, read_size, file.get())) > 0) {
253 manifest_str.append(buf, len);
254 if (len <= remainder)
255 break;
256 remainder -= len;
257 read_size = std::min(sizeof(buf), remainder);
258 }
259
260 // Verify the JSON
261 JSONStringValueSerializer json(manifest_str);
262 std::string error;
263 Value* val = json.Deserialize(&error);
264 if (!val) {
265 ReportExtensionInstallError(frontend, extension_path, error);
266 return NULL;
267 }
268 if (!val->IsType(Value::TYPE_DICTIONARY)) {
269 ReportExtensionInstallError(frontend, extension_path,
270 "manifest isn't a JSON dictionary");
271 return NULL;
272 }
273 DictionaryValue* manifest = static_cast<DictionaryValue*>(val);
274 std::string zip_hash;
275 if (!manifest->GetString(Extension::kZipHashKey, &zip_hash)) {
276 ReportExtensionInstallError(frontend, extension_path,
277 "missing zip_hash key");
278 return NULL;
279 }
280 if (zip_hash.size() != kZipHashHexBytes) {
281 ReportExtensionInstallError(frontend, extension_path,
282 "invalid zip_hash key");
283 return NULL;
284 }
285
286 // Read the rest of the zip file and compute a hash to compare against
287 // what the manifest claims. Compute the hash incrementally since the
288 // zip file could be large.
289 const unsigned char* ubuf = reinterpret_cast<const unsigned char*>(buf);
290 SHA256Context ctx;
291 SHA256_Begin(&ctx);
292 while ((len = fread(buf, 1, sizeof(buf), file.get())) > 0)
293 SHA256_Update(&ctx, ubuf, len);
294 uint8 hash[32];
295 SHA256_End(&ctx, hash, NULL, sizeof(hash));
296
297 std::vector<uint8> zip_hash_bytes;
298 if (!HexStringToBytes(zip_hash, &zip_hash_bytes)) {
299 ReportExtensionInstallError(frontend, extension_path,
300 "invalid zip_hash key");
301 return NULL;
302 }
303 if (zip_hash_bytes.size() != kZipHashBytes) {
304 ReportExtensionInstallError(frontend, extension_path,
305 "invalid zip_hash key");
306 return NULL;
307 }
308 for (size_t i = 0; i < kZipHashBytes; ++i) {
309 if (zip_hash_bytes[i] != hash[i]) {
310 ReportExtensionInstallError(frontend, extension_path,
311 "zip_hash key didn't match zip hash");
312 return NULL;
313 }
314 }
315
316 // TODO(erikkay): The manifest will also contain a signature of the hash
317 // (or perhaps the whole manifest) for authentication purposes.
318
319 return manifest;
320}
321
322bool ExtensionsServiceBackend::CheckCurrentVersion(
323 const FilePath& extension_path,
324 const std::string& version,
325 const FilePath& dest_dir,
326 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
327 FilePath current_version =
328 dest_dir.AppendASCII(ExtensionsService::kCurrentVersionFileName);
329 if (file_util::PathExists(current_version)) {
330 std::string version_str;
331 if (file_util::ReadFileToString(current_version, &version_str)) {
332 if (version_str == version) {
333 ReportExtensionInstallError(frontend, extension_path,
334 "Extension version already installed");
335 return false;
336 } else {
337 scoped_ptr<Version> cur_version(
338 Version::GetVersionFromString(version_str));
339 scoped_ptr<Version> new_version(
340 Version::GetVersionFromString(version));
341 if (cur_version->CompareTo(*new_version) >= 0) {
342 ReportExtensionInstallError(frontend, extension_path,
343 "More recent version of extension already installed");
344 return false;
345 }
346 }
347 }
348 }
349 return true;
350}
351
352bool ExtensionsServiceBackend::UnzipExtension(const FilePath& extension_path,
353 const FilePath& temp_dir,
354 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
355 // <profile>/Extensions/INSTALL_TEMP/<version>
356 if (!file_util::CreateDirectory(temp_dir)) {
357 ReportExtensionInstallError(frontend, extension_path,
358 "Couldn't create version directory.");
359 return false;
360 }
361 if (!Unzip(extension_path, temp_dir, NULL)) {
362 // Remove what we just installed.
363 file_util::Delete(temp_dir, true);
364 ReportExtensionInstallError(frontend, extension_path,
365 "Couldn't unzip extension.");
366 return false;
367 }
368 return true;
369}
370
371bool ExtensionsServiceBackend::InstallDirSafely(
372 const FilePath& extension_path,
373 const FilePath& source_dir,
374 const FilePath& dest_dir,
375 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
376
377 if (file_util::PathExists(dest_dir)) {
378 // By the time we get here, it should be safe to assume that this directory
379 // is not currently in use (it's not the current active version).
380 if (!file_util::Delete(dest_dir, true)) {
381 ReportExtensionInstallError(frontend, extension_path,
382 "Can't delete existing version directory.");
383 return false;
384 }
385 } else {
386 FilePath parent = dest_dir.DirName();
387 if (!file_util::DirectoryExists(parent)) {
388 if (!file_util::CreateDirectory(parent)) {
389 ReportExtensionInstallError(frontend, extension_path,
390 "Couldn't create extension directory.");
391 return false;
392 }
393 }
394 }
395 if (!file_util::Move(source_dir, dest_dir)) {
396 ReportExtensionInstallError(frontend, extension_path,
397 "Couldn't move temporary directory.");
398 return false;
399 }
400
401 return true;
402}
403
404bool ExtensionsServiceBackend::SetCurrentVersion(
405 const FilePath& extension_path,
406 const FilePath& dest_dir,
407 std::string version,
408 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
409 // Write out the new CurrentVersion file.
410 // <profile>/Extension/<name>/CurrentVersion
411 FilePath current_version =
412 dest_dir.AppendASCII(ExtensionsService::kCurrentVersionFileName);
413 FilePath current_version_old =
414 current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old"));
415 if (file_util::PathExists(current_version_old)) {
416 if (!file_util::Delete(current_version_old, false)) {
417 ReportExtensionInstallError(frontend, extension_path,
418 "Couldn't remove CurrentVersion_old file.");
419 return false;
420 }
421 }
422 if (file_util::PathExists(current_version)) {
423 if (!file_util::Move(current_version, current_version_old)) {
424 ReportExtensionInstallError(frontend, extension_path,
425 "Couldn't move CurrentVersion file.");
426 return false;
427 }
428 }
429 net::FileStream stream;
430 int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE;
431 if (stream.Open(current_version, flags) != 0)
432 return false;
433 if (stream.Write(version.c_str(), version.size(), NULL) < 0) {
434 // Restore the old CurrentVersion.
435 if (file_util::PathExists(current_version_old)) {
436 if (!file_util::Move(current_version_old, current_version)) {
437 LOG(WARNING) << "couldn't restore " << current_version_old.value() <<
438 " to " << current_version.value();
439 // TODO(erikkay): This is an ugly state to be in. Try harder?
440 }
441 }
442 ReportExtensionInstallError(frontend, extension_path,
443 "Couldn't create CurrentVersion file.");
444 return false;
445 }
446 return true;
447}
448
449bool ExtensionsServiceBackend::InstallExtension(
450 const FilePath& extension_path,
451 const FilePath& install_dir,
452 scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
453 LOG(INFO) << "Installing extension " << extension_path.value();
454
455 // <profile>/Extensions/INSTALL_TEMP
456 FilePath temp_dir = install_dir.AppendASCII(kTempExtensionName);
457 // Ensure we're starting with a clean slate.
458 if (file_util::PathExists(temp_dir)) {
459 if (!file_util::Delete(temp_dir, true)) {
460 ReportExtensionInstallError(frontend, extension_path,
461 "Couldn't delete existing temporary directory.");
462 return false;
463 }
464 }
465 ScopedTempDir scoped_temp;
466 scoped_temp.Set(temp_dir);
467 if (!scoped_temp.IsValid()) {
468 ReportExtensionInstallError(frontend, extension_path,
469 "Couldn't create temporary directory.");
470 return false;
471 }
472
473 // Read and verify the extension.
474 scoped_ptr<DictionaryValue> manifest(ReadManifest(extension_path, frontend));
475 if (!manifest.get()) {
476 // ReadManifest has already reported the extension error.
477 return false;
478 }
479 DictionaryValue* dict = manifest.get();
480 Extension extension;
481 std::string error;
482 if (!extension.InitFromValue(*dict, &error)) {
483 ReportExtensionInstallError(frontend, extension_path,
484 "Invalid extension manifest.");
485 return false;
486 }
487
488 // <profile>/Extensions/<id>
489 FilePath dest_dir = install_dir.AppendASCII(extension.id());
490 std::string version = extension.VersionString();
491 if (!CheckCurrentVersion(extension_path, version, dest_dir, frontend))
492 return false;
493
494 // <profile>/Extensions/INSTALL_TEMP/<version>
495 FilePath temp_version = temp_dir.AppendASCII(version);
496 if (!UnzipExtension(extension_path, temp_version, frontend))
497 return false;
498
499 // <profile>/Extensions/<dir_name>/<version>
500 FilePath version_dir = dest_dir.AppendASCII(version);
501 if (!InstallDirSafely(extension_path, temp_version, version_dir, frontend))
502 return false;
503
504 if (!SetCurrentVersion(extension_path, dest_dir, version, frontend)) {
505 if (!file_util::Delete(version_dir, true))
506 LOG(WARNING) << "Can't remove " << dest_dir.value();
507 return false;
508 }
509
510 ReportExtensionInstalled(frontend, dest_dir);
511 return true;
512}
513
514void ExtensionsServiceBackend::ReportExtensionInstallError(
515 ExtensionsServiceFrontendInterface *frontend, const FilePath& path,
516 const std::string &error) {
517 // TODO(erikkay): note that this isn't guaranteed to work properly on Linux.
518 std::string path_str = WideToASCII(path.ToWStringHack());
519 std::string message =
520 StringPrintf("Could not install extension from '%s'. %s",
521 path_str.c_str(), error.c_str());
522 frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
523 frontend, &ExtensionsServiceFrontendInterface::OnExtensionInstallError,
524 message));
525}
526
527void ExtensionsServiceBackend::ReportExtensionInstalled(
528 ExtensionsServiceFrontendInterface *frontend, FilePath path) {
529 frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
530 frontend,
531 &ExtensionsServiceFrontendInterface::OnExtensionInstalled,
532 path));
533}