OLD | NEW |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/common/extensions/extension_unpacker.h" | 5 #include "chrome/common/extensions/extension_unpacker.h" |
6 | 6 |
7 #include "base/file_util.h" | 7 #include "base/file_util.h" |
8 #include "base/scoped_handle.h" | 8 #include "base/scoped_handle.h" |
9 #include "base/scoped_temp_dir.h" | 9 #include "base/scoped_temp_dir.h" |
10 #include "base/string_util.h" | 10 #include "base/string_util.h" |
11 #include "base/third_party/nss/blapi.h" | 11 #include "base/third_party/nss/blapi.h" |
12 #include "base/third_party/nss/sha256.h" | 12 #include "base/third_party/nss/sha256.h" |
13 #include "base/thread.h" | 13 #include "base/thread.h" |
14 #include "base/values.h" | 14 #include "base/values.h" |
15 #include "net/base/file_stream.h" | 15 #include "net/base/file_stream.h" |
16 // TODO(mpcomplete): move to common | 16 // TODO(mpcomplete): move to common |
17 #include "chrome/browser/extensions/extension.h" | 17 #include "chrome/browser/extensions/extension.h" |
18 #include "chrome/common/json_value_serializer.h" | 18 #include "chrome/common/json_value_serializer.h" |
19 #include "chrome/common/notification_service.h" | 19 #include "chrome/common/notification_service.h" |
20 #include "chrome/common/unzip.h" | 20 #include "chrome/common/unzip.h" |
21 #include "chrome/common/url_constants.h" | 21 #include "chrome/common/url_constants.h" |
| 22 #include "third_party/skia/include/core/SkBitmap.h" |
| 23 #include "webkit/glue/image_decoder.h" |
22 | 24 |
23 namespace { | 25 namespace { |
24 const char kCurrentVersionFileName[] = "Current Version"; | 26 const char kCurrentVersionFileName[] = "Current Version"; |
25 | 27 |
26 // The name of a temporary directory to install an extension into for | 28 // The name of a temporary directory to install an extension into for |
27 // validation before finalizing install. | 29 // validation before finalizing install. |
28 const char kTempExtensionName[] = "TEMP_INSTALL"; | 30 const char kTempExtensionName[] = "TEMP_INSTALL"; |
29 | 31 |
30 // Chromium Extension magic number | 32 // Chromium Extension magic number |
31 const char kExtensionFileMagic[] = "Cr24"; | 33 const char kExtensionFileMagic[] = "Cr24"; |
(...skipping 20 matching lines...) Expand all Loading... |
52 const wchar_t kRegistryExtensionVersion[] = L"version"; | 54 const wchar_t kRegistryExtensionVersion[] = L"version"; |
53 | 55 |
54 #endif | 56 #endif |
55 | 57 |
56 // A marker file to indicate that an extension was installed from an external | 58 // A marker file to indicate that an extension was installed from an external |
57 // source. | 59 // source. |
58 const char kExternalInstallFile[] = "EXTERNAL_INSTALL"; | 60 const char kExternalInstallFile[] = "EXTERNAL_INSTALL"; |
59 | 61 |
60 // The version of the extension package that this code understands. | 62 // The version of the extension package that this code understands. |
61 const uint32 kExpectedVersion = 1; | 63 const uint32 kExpectedVersion = 1; |
| 64 } // namespace |
| 65 |
| 66 static SkBitmap DecodeImage(const FilePath& path) { |
| 67 // Read the file from disk. |
| 68 std::string file_contents; |
| 69 if (!file_util::PathExists(path) || |
| 70 !file_util::ReadFileToString(path, &file_contents)) { |
| 71 return SkBitmap(); |
| 72 } |
| 73 |
| 74 // Decode the image using WebKit's image decoder. |
| 75 const unsigned char* data = |
| 76 reinterpret_cast<const unsigned char*>(file_contents.data()); |
| 77 webkit_glue::ImageDecoder decoder; |
| 78 return decoder.Decode(data, file_contents.length()); |
| 79 } |
| 80 |
| 81 static bool PathContainsParentDirectory(const FilePath& path) { |
| 82 const FilePath::StringType kSeparators(FilePath::kSeparators); |
| 83 const FilePath::StringType kParentDirectory(FilePath::kParentDirectory); |
| 84 const size_t npos = FilePath::StringType::npos; |
| 85 const FilePath::StringType& value = path.value(); |
| 86 |
| 87 for (size_t i = 0; i < value.length(); ) { |
| 88 i = value.find(kParentDirectory, i); |
| 89 if (i != npos) { |
| 90 if ((i == 0 || kSeparators.find(value[i-1]) == npos) && |
| 91 (i+1 < value.length() || kSeparators.find(value[i+1]) == npos)) { |
| 92 return true; |
| 93 } |
| 94 ++i; |
| 95 } |
| 96 } |
| 97 |
| 98 return false; |
62 } | 99 } |
63 | 100 |
64 // The extension file format is a header, followed by the manifest, followed | 101 // The extension file format is a header, followed by the manifest, followed |
65 // by the zip file. The header is a magic number, a version, the size of the | 102 // by the zip file. The header is a magic number, a version, the size of the |
66 // header, and the size of the manifest. These ints are 4 byte little endian. | 103 // header, and the size of the manifest. These ints are 4 byte little endian. |
67 DictionaryValue* ExtensionUnpacker::ReadManifest() { | 104 DictionaryValue* ExtensionUnpacker::ReadPackageHeader() { |
68 ScopedStdioHandle file(file_util::OpenFile(extension_path_, "rb")); | 105 ScopedStdioHandle file(file_util::OpenFile(extension_path_, "rb")); |
69 if (!file.get()) { | 106 if (!file.get()) { |
70 SetError("no such extension file"); | 107 SetError("no such extension file"); |
71 return NULL; | 108 return NULL; |
72 } | 109 } |
73 | 110 |
74 // Read and verify the header. | 111 // Read and verify the header. |
75 ExtensionHeader header; | 112 ExtensionHeader header; |
76 size_t len; | 113 size_t len; |
77 | 114 |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
159 } | 196 } |
160 | 197 |
161 // TODO(erikkay): The manifest will also contain a signature of the hash | 198 // TODO(erikkay): The manifest will also contain a signature of the hash |
162 // (or perhaps the whole manifest) for authentication purposes. | 199 // (or perhaps the whole manifest) for authentication purposes. |
163 | 200 |
164 // The caller owns val (now cast to manifest). | 201 // The caller owns val (now cast to manifest). |
165 val.release(); | 202 val.release(); |
166 return manifest; | 203 return manifest; |
167 } | 204 } |
168 | 205 |
| 206 DictionaryValue* ExtensionUnpacker::ReadManifest() { |
| 207 FilePath manifest_path = |
| 208 temp_install_dir_.AppendASCII(Extension::kManifestFilename); |
| 209 if (!file_util::PathExists(manifest_path)) { |
| 210 SetError(Extension::kInvalidManifestError); |
| 211 return NULL; |
| 212 } |
| 213 |
| 214 JSONFileValueSerializer serializer(manifest_path); |
| 215 std::string error; |
| 216 scoped_ptr<Value> root(serializer.Deserialize(&error)); |
| 217 if (!root.get()) { |
| 218 SetError(error); |
| 219 return NULL; |
| 220 } |
| 221 |
| 222 if (!root->IsType(Value::TYPE_DICTIONARY)) { |
| 223 SetError(Extension::kInvalidManifestError); |
| 224 return NULL; |
| 225 } |
| 226 |
| 227 return static_cast<DictionaryValue*>(root.release()); |
| 228 } |
| 229 |
169 bool ExtensionUnpacker::Run() { | 230 bool ExtensionUnpacker::Run() { |
170 LOG(INFO) << "Installing extension " << extension_path_.value(); | 231 LOG(INFO) << "Installing extension " << extension_path_.value(); |
171 | 232 |
172 // Read and verify the extension. | 233 // Read and verify the extension. |
173 scoped_ptr<DictionaryValue> manifest(ReadManifest()); | 234 scoped_ptr<DictionaryValue> header_manifest(ReadPackageHeader()); |
174 if (!manifest.get()) { | 235 if (!header_manifest.get()) { |
175 // ReadManifest has already reported the extension error. | 236 // ReadPackageHeader has already reported the extension error. |
176 return false; | |
177 } | |
178 Extension extension; | |
179 std::string error; | |
180 if (!extension.InitFromValue(*manifest, | |
181 true, // require ID | |
182 &error)) { | |
183 SetError("Invalid extension manifest."); | |
184 return false; | 237 return false; |
185 } | 238 } |
186 | 239 |
187 // ID is required for installed extensions. | 240 // TODO(mpcomplete): it looks like this isn't actually necessary. We don't |
188 if (extension.id().empty()) { | 241 // use header_extension, and we check that the unzipped manifest is valid. |
189 SetError("Required value 'id' is missing."); | 242 Extension header_extension; |
| 243 std::string error; |
| 244 if (!header_extension.InitFromValue(*header_manifest, |
| 245 true, // require ID |
| 246 &error)) { |
| 247 SetError(error); |
190 return false; | 248 return false; |
191 } | 249 } |
192 | 250 |
193 // <profile>/Extensions/INSTALL_TEMP/<version> | 251 // <profile>/Extensions/INSTALL_TEMP/<version> |
194 std::string version = extension.VersionString(); | 252 temp_install_dir_ = |
195 FilePath temp_install = | |
196 extension_path_.DirName().AppendASCII(kTempExtensionName); | 253 extension_path_.DirName().AppendASCII(kTempExtensionName); |
197 if (!file_util::CreateDirectory(temp_install)) { | 254 if (!file_util::CreateDirectory(temp_install_dir_)) { |
198 SetError("Couldn't create directory for unzipping."); | 255 SetError("Couldn't create directory for unzipping."); |
199 return false; | 256 return false; |
200 } | 257 } |
201 | 258 |
202 if (!Unzip(extension_path_, temp_install, NULL)) { | 259 if (!Unzip(extension_path_, temp_install_dir_, NULL)) { |
203 SetError("Couldn't unzip extension."); | 260 SetError("Couldn't unzip extension."); |
204 return false; | 261 return false; |
205 } | 262 } |
206 | 263 |
| 264 // Parse the manifest. |
| 265 parsed_manifest_.reset(ReadManifest()); |
| 266 if (!parsed_manifest_.get()) |
| 267 return false; // Error was already reported. |
| 268 |
| 269 // Re-read the actual manifest into our extension struct. |
| 270 Extension extension; |
| 271 if (!extension.InitFromValue(*parsed_manifest_, |
| 272 true, // require ID |
| 273 &error)) { |
| 274 SetError(error); |
| 275 return false; |
| 276 } |
| 277 |
| 278 // Decode any images that the browser needs to display. |
| 279 DictionaryValue* images = extension.GetThemeImages(); |
| 280 if (images) { |
| 281 for (DictionaryValue::key_iterator it = images->begin_keys(); |
| 282 it != images->end_keys(); ++it) { |
| 283 std::wstring val; |
| 284 if (images->GetString(*it, &val)) { |
| 285 if (!AddDecodedImage(FilePath::FromWStringHack(val))) |
| 286 return false; // Error was already reported. |
| 287 } |
| 288 } |
| 289 } |
| 290 |
| 291 for (PageActionMap::const_iterator it = extension.page_actions().begin(); |
| 292 it != extension.page_actions().end(); ++it) { |
| 293 if (!AddDecodedImage(it->second->icon_path())) |
| 294 return false; // Error was already reported. |
| 295 } |
| 296 |
| 297 return true; |
| 298 } |
| 299 |
| 300 bool ExtensionUnpacker::AddDecodedImage(const FilePath& path) { |
| 301 // Make sure it's not referencing a file outside the extension's subdir. |
| 302 if (path.IsAbsolute() || PathContainsParentDirectory(path)) { |
| 303 SetError("Path names must not be absolute or contain '..'."); |
| 304 return false; |
| 305 } |
| 306 |
| 307 SkBitmap image_bitmap = DecodeImage(temp_install_dir_.Append(path)); |
| 308 if (image_bitmap.isNull()) { |
| 309 SetError("Could not decode theme image."); |
| 310 return false; |
| 311 } |
| 312 |
| 313 decoded_images_.push_back(MakeTuple(image_bitmap, path)); |
207 return true; | 314 return true; |
208 } | 315 } |
209 | 316 |
210 void ExtensionUnpacker::SetError(const std::string &error) { | 317 void ExtensionUnpacker::SetError(const std::string &error) { |
211 error_message_ = error; | 318 error_message_ = error; |
212 } | 319 } |
OLD | NEW |