OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 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_file_util.h" | 5 #include "chrome/common/extensions/extension_file_util.h" |
6 | 6 |
7 #include <map> | 7 #include "base/files/file_path.h" |
8 #include <vector> | 8 #include "base/values.h" |
9 | |
10 #include "base/file_util.h" | |
11 #include "base/files/file_enumerator.h" | |
12 #include "base/files/scoped_temp_dir.h" | |
13 #include "base/json/json_file_value_serializer.h" | |
14 #include "base/logging.h" | |
15 #include "base/metrics/histogram.h" | |
16 #include "base/path_service.h" | |
17 #include "base/strings/stringprintf.h" | |
18 #include "base/threading/thread_restrictions.h" | |
19 #include "chrome/common/chrome_paths.h" | |
20 #include "chrome/common/extensions/api/extension_action/action_info.h" | 9 #include "chrome/common/extensions/api/extension_action/action_info.h" |
21 #include "chrome/common/extensions/manifest_handlers/theme_handler.h" | 10 #include "chrome/common/extensions/manifest_handlers/theme_handler.h" |
22 #include "extensions/common/constants.h" | 11 #include "extensions/common/constants.h" |
23 #include "extensions/common/extension.h" | 12 #include "extensions/common/extension.h" |
24 #include "extensions/common/extension_icon_set.h" | 13 #include "extensions/common/extension_icon_set.h" |
25 #include "extensions/common/extension_l10n_util.h" | |
26 #include "extensions/common/extension_messages.h" | |
27 #include "extensions/common/extension_resource.h" | |
28 #include "extensions/common/install_warning.h" | |
29 #include "extensions/common/manifest.h" | |
30 #include "extensions/common/manifest_constants.h" | |
31 #include "extensions/common/manifest_handler.h" | |
32 #include "extensions/common/manifest_handlers/icons_handler.h" | 14 #include "extensions/common/manifest_handlers/icons_handler.h" |
33 #include "grit/generated_resources.h" | |
34 #include "net/base/file_stream.h" | |
35 #include "ui/base/l10n/l10n_util.h" | |
36 | 15 |
37 using extensions::Extension; | 16 using extensions::Extension; |
38 using extensions::ExtensionResource; | 17 using extensions::ExtensionResource; |
39 using extensions::Manifest; | 18 using extensions::Manifest; |
40 | 19 |
41 namespace errors = extensions::manifest_errors; | |
42 | |
43 namespace { | 20 namespace { |
44 | 21 |
45 // Add the image paths contained in the |icon_set| to |image_paths|. | 22 // Add the image paths contained in the |icon_set| to |image_paths|. |
46 void AddPathsFromIconSet(const ExtensionIconSet& icon_set, | 23 void AddPathsFromIconSet(const ExtensionIconSet& icon_set, |
47 std::set<base::FilePath>* image_paths) { | 24 std::set<base::FilePath>* image_paths) { |
48 // TODO(viettrungluu): These |FilePath::FromUTF8Unsafe()| indicate that we're | 25 // TODO(viettrungluu): These |FilePath::FromUTF8Unsafe()| indicate that we're |
49 // doing something wrong. | 26 // doing something wrong. |
50 for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin(); | 27 for (ExtensionIconSet::IconMap::const_iterator iter = icon_set.map().begin(); |
51 iter != icon_set.map().end(); ++iter) { | 28 iter != icon_set.map().end(); ++iter) { |
52 image_paths->insert(base::FilePath::FromUTF8Unsafe(iter->second)); | 29 image_paths->insert(base::FilePath::FromUTF8Unsafe(iter->second)); |
53 } | 30 } |
54 } | 31 } |
55 | 32 |
56 } // namespace | 33 } // namespace |
57 | 34 |
58 namespace extension_file_util { | 35 namespace extension_file_util { |
59 | 36 |
60 const base::FilePath::CharType kTempDirectoryName[] = FILE_PATH_LITERAL("Temp"); | |
61 | |
62 base::FilePath InstallExtension(const base::FilePath& unpacked_source_dir, | |
63 const std::string& id, | |
64 const std::string& version, | |
65 const base::FilePath& extensions_dir) { | |
66 base::FilePath extension_dir = extensions_dir.AppendASCII(id); | |
67 base::FilePath version_dir; | |
68 | |
69 // Create the extension directory if it doesn't exist already. | |
70 if (!base::PathExists(extension_dir)) { | |
71 if (!base::CreateDirectory(extension_dir)) | |
72 return base::FilePath(); | |
73 } | |
74 | |
75 // Get a temp directory on the same file system as the profile. | |
76 base::FilePath install_temp_dir = GetInstallTempDir(extensions_dir); | |
77 base::ScopedTempDir extension_temp_dir; | |
78 if (install_temp_dir.empty() || | |
79 !extension_temp_dir.CreateUniqueTempDirUnderPath(install_temp_dir)) { | |
80 LOG(ERROR) << "Creating of temp dir under in the profile failed."; | |
81 return base::FilePath(); | |
82 } | |
83 base::FilePath crx_temp_source = | |
84 extension_temp_dir.path().Append(unpacked_source_dir.BaseName()); | |
85 if (!base::Move(unpacked_source_dir, crx_temp_source)) { | |
86 LOG(ERROR) << "Moving extension from : " << unpacked_source_dir.value() | |
87 << " to : " << crx_temp_source.value() << " failed."; | |
88 return base::FilePath(); | |
89 } | |
90 | |
91 // Try to find a free directory. There can be legitimate conflicts in the case | |
92 // of overinstallation of the same version. | |
93 const int kMaxAttempts = 100; | |
94 for (int i = 0; i < kMaxAttempts; ++i) { | |
95 base::FilePath candidate = extension_dir.AppendASCII( | |
96 base::StringPrintf("%s_%u", version.c_str(), i)); | |
97 if (!base::PathExists(candidate)) { | |
98 version_dir = candidate; | |
99 break; | |
100 } | |
101 } | |
102 | |
103 if (version_dir.empty()) { | |
104 LOG(ERROR) << "Could not find a home for extension " << id << " with " | |
105 << "version " << version << "."; | |
106 return base::FilePath(); | |
107 } | |
108 | |
109 if (!base::Move(crx_temp_source, version_dir)) { | |
110 LOG(ERROR) << "Installing extension from : " << crx_temp_source.value() | |
111 << " into : " << version_dir.value() << " failed."; | |
112 return base::FilePath(); | |
113 } | |
114 | |
115 return version_dir; | |
116 } | |
117 | |
118 void UninstallExtension(const base::FilePath& extensions_dir, | |
119 const std::string& id) { | |
120 // We don't care about the return value. If this fails (and it can, due to | |
121 // plugins that aren't unloaded yet), it will get cleaned up by | |
122 // ExtensionGarbageCollector::GarbageCollectExtensions. | |
123 base::DeleteFile(extensions_dir.AppendASCII(id), true); // recursive. | |
124 } | |
125 | |
126 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path, | |
127 Manifest::Location location, | |
128 int flags, | |
129 std::string* error) { | |
130 return LoadExtension(extension_path, std::string(), location, flags, error); | |
131 } | |
132 | |
133 scoped_refptr<Extension> LoadExtension(const base::FilePath& extension_path, | |
134 const std::string& extension_id, | |
135 Manifest::Location location, | |
136 int flags, | |
137 std::string* error) { | |
138 scoped_ptr<base::DictionaryValue> manifest( | |
139 LoadManifest(extension_path, error)); | |
140 if (!manifest.get()) | |
141 return NULL; | |
142 if (!extension_l10n_util::LocalizeExtension(extension_path, manifest.get(), | |
143 error)) { | |
144 return NULL; | |
145 } | |
146 | |
147 scoped_refptr<Extension> extension(Extension::Create(extension_path, | |
148 location, | |
149 *manifest, | |
150 flags, | |
151 extension_id, | |
152 error)); | |
153 if (!extension.get()) | |
154 return NULL; | |
155 | |
156 std::vector<extensions::InstallWarning> warnings; | |
157 if (!ValidateExtension(extension.get(), error, &warnings)) | |
158 return NULL; | |
159 extension->AddInstallWarnings(warnings); | |
160 | |
161 return extension; | |
162 } | |
163 | |
164 base::DictionaryValue* LoadManifest(const base::FilePath& extension_path, | |
165 std::string* error) { | |
166 base::FilePath manifest_path = | |
167 extension_path.Append(extensions::kManifestFilename); | |
168 if (!base::PathExists(manifest_path)) { | |
169 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); | |
170 return NULL; | |
171 } | |
172 | |
173 JSONFileValueSerializer serializer(manifest_path); | |
174 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, error)); | |
175 if (!root.get()) { | |
176 if (error->empty()) { | |
177 // If |error| is empty, than the file could not be read. | |
178 // It would be cleaner to have the JSON reader give a specific error | |
179 // in this case, but other code tests for a file error with | |
180 // error->empty(). For now, be consistent. | |
181 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_UNREADABLE); | |
182 } else { | |
183 *error = base::StringPrintf("%s %s", | |
184 errors::kManifestParseError, | |
185 error->c_str()); | |
186 } | |
187 return NULL; | |
188 } | |
189 | |
190 if (!root->IsType(base::Value::TYPE_DICTIONARY)) { | |
191 *error = l10n_util::GetStringUTF8(IDS_EXTENSION_MANIFEST_INVALID); | |
192 return NULL; | |
193 } | |
194 | |
195 return static_cast<base::DictionaryValue*>(root.release()); | |
196 } | |
197 | |
198 std::vector<base::FilePath> FindPrivateKeyFiles( | |
199 const base::FilePath& extension_dir) { | |
200 std::vector<base::FilePath> result; | |
201 // Pattern matching only works at the root level, so filter manually. | |
202 base::FileEnumerator traversal(extension_dir, /*recursive=*/true, | |
203 base::FileEnumerator::FILES); | |
204 for (base::FilePath current = traversal.Next(); !current.empty(); | |
205 current = traversal.Next()) { | |
206 if (!current.MatchesExtension(extensions::kExtensionKeyFileExtension)) | |
207 continue; | |
208 | |
209 std::string key_contents; | |
210 if (!base::ReadFileToString(current, &key_contents)) { | |
211 // If we can't read the file, assume it's not a private key. | |
212 continue; | |
213 } | |
214 std::string key_bytes; | |
215 if (!Extension::ParsePEMKeyBytes(key_contents, &key_bytes)) { | |
216 // If we can't parse the key, assume it's ok too. | |
217 continue; | |
218 } | |
219 | |
220 result.push_back(current); | |
221 } | |
222 return result; | |
223 } | |
224 | |
225 bool ValidateExtension(const Extension* extension, | |
226 std::string* error, | |
227 std::vector<extensions::InstallWarning>* warnings) { | |
228 // Ask registered manifest handlers to validate their paths. | |
229 if (!extensions::ManifestHandler::ValidateExtension( | |
230 extension, error, warnings)) | |
231 return false; | |
232 | |
233 // Check children of extension root to see if any of them start with _ and is | |
234 // not on the reserved list. We only warn, and do not block the loading of the | |
235 // extension. | |
236 std::string warning; | |
237 if (!CheckForIllegalFilenames(extension->path(), &warning)) | |
238 warnings->push_back(extensions::InstallWarning(warning)); | |
239 | |
240 // Check that extensions don't include private key files. | |
241 std::vector<base::FilePath> private_keys = | |
242 FindPrivateKeyFiles(extension->path()); | |
243 if (extension->creation_flags() & Extension::ERROR_ON_PRIVATE_KEY) { | |
244 if (!private_keys.empty()) { | |
245 // Only print one of the private keys because l10n_util doesn't have a way | |
246 // to translate a list of strings. | |
247 *error = l10n_util::GetStringFUTF8( | |
248 IDS_EXTENSION_CONTAINS_PRIVATE_KEY, | |
249 private_keys.front().LossyDisplayName()); | |
250 return false; | |
251 } | |
252 } else { | |
253 for (size_t i = 0; i < private_keys.size(); ++i) { | |
254 warnings->push_back(extensions::InstallWarning( | |
255 l10n_util::GetStringFUTF8( | |
256 IDS_EXTENSION_CONTAINS_PRIVATE_KEY, | |
257 private_keys[i].LossyDisplayName()))); | |
258 } | |
259 // Only warn; don't block loading the extension. | |
260 } | |
261 return true; | |
262 } | |
263 | |
264 std::set<base::FilePath> GetBrowserImagePaths(const Extension* extension) { | 37 std::set<base::FilePath> GetBrowserImagePaths(const Extension* extension) { |
265 std::set<base::FilePath> image_paths; | 38 std::set<base::FilePath> image_paths; |
266 | 39 |
267 AddPathsFromIconSet(extensions::IconsInfo::GetIcons(extension), &image_paths); | 40 AddPathsFromIconSet(extensions::IconsInfo::GetIcons(extension), &image_paths); |
268 | 41 |
269 // Theme images | 42 // Theme images |
270 const base::DictionaryValue* theme_images = | 43 const base::DictionaryValue* theme_images = |
271 extensions::ThemeInfo::GetImages(extension); | 44 extensions::ThemeInfo::GetImages(extension); |
272 if (theme_images) { | 45 if (theme_images) { |
273 for (base::DictionaryValue::Iterator it(*theme_images); !it.IsAtEnd(); | 46 for (base::DictionaryValue::Iterator it(*theme_images); !it.IsAtEnd(); |
(...skipping 10 matching lines...) Expand all Loading... |
284 AddPathsFromIconSet(page_action->default_icon, &image_paths); | 57 AddPathsFromIconSet(page_action->default_icon, &image_paths); |
285 | 58 |
286 const extensions::ActionInfo* browser_action = | 59 const extensions::ActionInfo* browser_action = |
287 extensions::ActionInfo::GetBrowserActionInfo(extension); | 60 extensions::ActionInfo::GetBrowserActionInfo(extension); |
288 if (browser_action && !browser_action->default_icon.empty()) | 61 if (browser_action && !browser_action->default_icon.empty()) |
289 AddPathsFromIconSet(browser_action->default_icon, &image_paths); | 62 AddPathsFromIconSet(browser_action->default_icon, &image_paths); |
290 | 63 |
291 return image_paths; | 64 return image_paths; |
292 } | 65 } |
293 | 66 |
294 bool CheckForIllegalFilenames(const base::FilePath& extension_path, | |
295 std::string* error) { | |
296 // Reserved underscore names. | |
297 static const base::FilePath::CharType* reserved_names[] = { | |
298 extensions::kLocaleFolder, | |
299 extensions::kPlatformSpecificFolder, | |
300 FILE_PATH_LITERAL("__MACOSX"), | |
301 }; | |
302 CR_DEFINE_STATIC_LOCAL( | |
303 std::set<base::FilePath::StringType>, reserved_underscore_names, | |
304 (reserved_names, reserved_names + arraysize(reserved_names))); | |
305 | |
306 // Enumerate all files and directories in the extension root. | |
307 // There is a problem when using pattern "_*" with FileEnumerator, so we have | |
308 // to cheat with find_first_of and match all. | |
309 const int kFilesAndDirectories = | |
310 base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES; | |
311 base::FileEnumerator all_files(extension_path, false, kFilesAndDirectories); | |
312 | |
313 base::FilePath file; | |
314 while (!(file = all_files.Next()).empty()) { | |
315 base::FilePath::StringType filename = file.BaseName().value(); | |
316 // Skip all that don't start with "_". | |
317 if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue; | |
318 if (reserved_underscore_names.find(filename) == | |
319 reserved_underscore_names.end()) { | |
320 *error = base::StringPrintf( | |
321 "Cannot load extension with file or directory name %s. " | |
322 "Filenames starting with \"_\" are reserved for use by the system.", | |
323 file.BaseName().AsUTF8Unsafe().c_str()); | |
324 return false; | |
325 } | |
326 } | |
327 | |
328 return true; | |
329 } | |
330 | |
331 base::FilePath GetInstallTempDir(const base::FilePath& extensions_dir) { | |
332 // We do file IO in this function, but only when the current profile's | |
333 // Temp directory has never been used before, or in a rare error case. | |
334 // Developers are not likely to see these situations often, so do an | |
335 // explicit thread check. | |
336 base::ThreadRestrictions::AssertIOAllowed(); | |
337 | |
338 // Create the temp directory as a sub-directory of the Extensions directory. | |
339 // This guarantees it is on the same file system as the extension's eventual | |
340 // install target. | |
341 base::FilePath temp_path = extensions_dir.Append(kTempDirectoryName); | |
342 if (base::PathExists(temp_path)) { | |
343 if (!base::DirectoryExists(temp_path)) { | |
344 DLOG(WARNING) << "Not a directory: " << temp_path.value(); | |
345 return base::FilePath(); | |
346 } | |
347 if (!base::PathIsWritable(temp_path)) { | |
348 DLOG(WARNING) << "Can't write to path: " << temp_path.value(); | |
349 return base::FilePath(); | |
350 } | |
351 // This is a directory we can write to. | |
352 return temp_path; | |
353 } | |
354 | |
355 // Directory doesn't exist, so create it. | |
356 if (!base::CreateDirectory(temp_path)) { | |
357 DLOG(WARNING) << "Couldn't create directory: " << temp_path.value(); | |
358 return base::FilePath(); | |
359 } | |
360 return temp_path; | |
361 } | |
362 | |
363 void DeleteFile(const base::FilePath& path, bool recursive) { | |
364 base::DeleteFile(path, recursive); | |
365 } | |
366 | |
367 } // namespace extension_file_util | 67 } // namespace extension_file_util |
OLD | NEW |