OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/api/file_handlers/app_file_handler_util.h" | |
6 | |
7 #include "base/files/file.h" | |
8 #include "base/files/file_path.h" | |
9 #include "base/files/file_util.h" | |
10 #include "build/build_config.h" | |
11 #include "chrome/browser/profiles/profile.h" | |
12 #include "content/public/browser/browser_thread.h" | |
13 #include "content/public/browser/child_process_security_policy.h" | |
14 #include "extensions/browser/api/extensions_api_client.h" | |
15 #include "extensions/browser/entry_info.h" | |
16 #include "extensions/browser/extension_prefs.h" | |
17 #include "extensions/browser/granted_file_entry.h" | |
18 #include "extensions/common/permissions/permissions_data.h" | |
19 #include "net/base/mime_util.h" | |
20 #include "storage/browser/fileapi/isolated_context.h" | |
21 #include "storage/common/fileapi/file_system_mount_option.h" | |
22 #include "storage/common/fileapi/file_system_types.h" | |
23 | |
24 #if defined(OS_CHROMEOS) | |
25 #include "extensions/browser/api/file_handlers/non_native_file_system_delegate.h
" | |
26 #endif | |
27 | |
28 namespace extensions { | |
29 | |
30 namespace app_file_handler_util { | |
31 | |
32 const char kInvalidParameters[] = "Invalid parameters"; | |
33 const char kSecurityError[] = "Security error"; | |
34 | |
35 namespace { | |
36 | |
37 bool FileHandlerCanHandleFileWithExtension(const FileHandlerInfo& handler, | |
38 const base::FilePath& path) { | |
39 for (std::set<std::string>::const_iterator extension = | |
40 handler.extensions.begin(); | |
41 extension != handler.extensions.end(); ++extension) { | |
42 if (*extension == "*") | |
43 return true; | |
44 | |
45 // Accept files whose extension or combined extension (e.g. ".tar.gz") | |
46 // match the supported extensions of file handler. | |
47 base::FilePath::StringType handler_extention( | |
48 base::FilePath::kExtensionSeparator + | |
49 base::FilePath::FromUTF8Unsafe(*extension).value()); | |
50 if (base::FilePath::CompareEqualIgnoreCase(handler_extention, | |
51 path.Extension()) || | |
52 base::FilePath::CompareEqualIgnoreCase(handler_extention, | |
53 path.FinalExtension())) { | |
54 return true; | |
55 } | |
56 | |
57 // Also accept files with no extension for handlers that support an | |
58 // empty extension, i.e. both "foo" and "foo." match. | |
59 if (extension->empty() && | |
60 path.MatchesExtension(base::FilePath::StringType())) { | |
61 return true; | |
62 } | |
63 } | |
64 return false; | |
65 } | |
66 | |
67 bool FileHandlerCanHandleFileWithMimeType(const FileHandlerInfo& handler, | |
68 const std::string& mime_type) { | |
69 for (std::set<std::string>::const_iterator type = handler.types.begin(); | |
70 type != handler.types.end(); ++type) { | |
71 if (net::MatchesMimeType(*type, mime_type)) | |
72 return true; | |
73 } | |
74 return false; | |
75 } | |
76 | |
77 bool PrepareNativeLocalFileForWritableApp(const base::FilePath& path, | |
78 bool is_directory) { | |
79 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE); | |
80 | |
81 // Don't allow links. | |
82 if (base::PathExists(path) && base::IsLink(path)) | |
83 return false; | |
84 | |
85 if (is_directory) | |
86 return base::DirectoryExists(path); | |
87 | |
88 // Create the file if it doesn't already exist. | |
89 int creation_flags = base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ; | |
90 base::File file(path, creation_flags); | |
91 | |
92 return file.IsValid(); | |
93 } | |
94 | |
95 // Checks whether a list of paths are all OK for writing and calls a provided | |
96 // on_success or on_failure callback when done. A path is OK for writing if it | |
97 // is not a symlink, is not in a blacklisted path and can be opened for writing. | |
98 // Creates files if they do not exist, but fails for non-existent directory | |
99 // paths. On Chrome OS, also fails for non-local files that don't already exist. | |
100 class WritableFileChecker | |
101 : public base::RefCountedThreadSafe<WritableFileChecker> { | |
102 public: | |
103 WritableFileChecker( | |
104 const std::vector<base::FilePath>& paths, | |
105 Profile* profile, | |
106 const std::set<base::FilePath>& directory_paths, | |
107 const base::Closure& on_success, | |
108 const base::Callback<void(const base::FilePath&)>& on_failure); | |
109 | |
110 void Check(); | |
111 | |
112 private: | |
113 friend class base::RefCountedThreadSafe<WritableFileChecker>; | |
114 virtual ~WritableFileChecker(); | |
115 | |
116 // Called when a work item is completed. If all work items are done, this | |
117 // calls the success or failure callback. | |
118 void TaskDone(); | |
119 | |
120 // Reports an error in completing a work item. This may be called more than | |
121 // once, but only the last message will be retained. | |
122 void Error(const base::FilePath& error_path); | |
123 | |
124 void CheckLocalWritableFiles(); | |
125 | |
126 // Called when processing a file is completed with either a success or an | |
127 // error. | |
128 void OnPrepareFileDone(const base::FilePath& path, bool success); | |
129 | |
130 const std::vector<base::FilePath> paths_; | |
131 Profile* profile_; | |
132 const std::set<base::FilePath> directory_paths_; | |
133 int outstanding_tasks_; | |
134 base::FilePath error_path_; | |
135 base::Closure on_success_; | |
136 base::Callback<void(const base::FilePath&)> on_failure_; | |
137 }; | |
138 | |
139 WritableFileChecker::WritableFileChecker( | |
140 const std::vector<base::FilePath>& paths, | |
141 Profile* profile, | |
142 const std::set<base::FilePath>& directory_paths, | |
143 const base::Closure& on_success, | |
144 const base::Callback<void(const base::FilePath&)>& on_failure) | |
145 : paths_(paths), | |
146 profile_(profile), | |
147 directory_paths_(directory_paths), | |
148 outstanding_tasks_(1), | |
149 on_success_(on_success), | |
150 on_failure_(on_failure) {} | |
151 | |
152 void WritableFileChecker::Check() { | |
153 outstanding_tasks_ = paths_.size(); | |
154 for (const auto& path : paths_) { | |
155 bool is_directory = directory_paths_.find(path) != directory_paths_.end(); | |
156 #if defined(OS_CHROMEOS) | |
157 NonNativeFileSystemDelegate* delegate = | |
158 ExtensionsAPIClient::Get()->GetNonNativeFileSystemDelegate(); | |
159 if (delegate && delegate->IsUnderNonNativeLocalPath(profile_, path)) { | |
160 if (is_directory) { | |
161 delegate->IsNonNativeLocalPathDirectory( | |
162 profile_, | |
163 path, | |
164 base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); | |
165 } else { | |
166 delegate->PrepareNonNativeLocalFileForWritableApp( | |
167 profile_, | |
168 path, | |
169 base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); | |
170 } | |
171 continue; | |
172 } | |
173 #endif | |
174 content::BrowserThread::PostTaskAndReplyWithResult( | |
175 content::BrowserThread::FILE, FROM_HERE, | |
176 base::Bind(&PrepareNativeLocalFileForWritableApp, path, is_directory), | |
177 base::Bind(&WritableFileChecker::OnPrepareFileDone, this, path)); | |
178 } | |
179 } | |
180 | |
181 WritableFileChecker::~WritableFileChecker() {} | |
182 | |
183 void WritableFileChecker::TaskDone() { | |
184 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
185 if (--outstanding_tasks_ == 0) { | |
186 if (error_path_.empty()) | |
187 on_success_.Run(); | |
188 else | |
189 on_failure_.Run(error_path_); | |
190 } | |
191 } | |
192 | |
193 // Reports an error in completing a work item. This may be called more than | |
194 // once, but only the last message will be retained. | |
195 void WritableFileChecker::Error(const base::FilePath& error_path) { | |
196 DCHECK(!error_path.empty()); | |
197 error_path_ = error_path; | |
198 TaskDone(); | |
199 } | |
200 | |
201 void WritableFileChecker::OnPrepareFileDone(const base::FilePath& path, | |
202 bool success) { | |
203 if (success) | |
204 TaskDone(); | |
205 else | |
206 Error(path); | |
207 } | |
208 | |
209 } // namespace | |
210 | |
211 const FileHandlerInfo* FileHandlerForId(const Extension& app, | |
212 const std::string& handler_id) { | |
213 const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app); | |
214 if (!file_handlers) | |
215 return NULL; | |
216 | |
217 for (FileHandlersInfo::const_iterator i = file_handlers->begin(); | |
218 i != file_handlers->end(); i++) { | |
219 if (i->id == handler_id) | |
220 return &*i; | |
221 } | |
222 return NULL; | |
223 } | |
224 | |
225 std::vector<const FileHandlerInfo*> FindFileHandlersForEntries( | |
226 const Extension& app, | |
227 const std::vector<EntryInfo> entries) { | |
228 std::vector<const FileHandlerInfo*> handlers; | |
229 if (entries.empty()) | |
230 return handlers; | |
231 | |
232 // Look for file handlers which can handle all the MIME types specified. | |
233 const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app); | |
234 if (!file_handlers) | |
235 return handlers; | |
236 | |
237 for (FileHandlersInfo::const_iterator data = file_handlers->begin(); | |
238 data != file_handlers->end(); ++data) { | |
239 bool handles_all_types = true; | |
240 for (std::vector<EntryInfo>::const_iterator it = entries.begin(); | |
241 it != entries.end(); ++it) { | |
242 if (!FileHandlerCanHandleEntry(*data, *it)) { | |
243 handles_all_types = false; | |
244 break; | |
245 } | |
246 } | |
247 if (handles_all_types) | |
248 handlers.push_back(&*data); | |
249 } | |
250 return handlers; | |
251 } | |
252 | |
253 bool FileHandlerCanHandleEntry(const FileHandlerInfo& handler, | |
254 const EntryInfo& entry) { | |
255 if (entry.is_directory) | |
256 return handler.include_directories; | |
257 | |
258 return FileHandlerCanHandleFileWithMimeType(handler, entry.mime_type) || | |
259 FileHandlerCanHandleFileWithExtension(handler, entry.path); | |
260 } | |
261 | |
262 GrantedFileEntry CreateFileEntry(Profile* profile, | |
263 const Extension* extension, | |
264 int renderer_id, | |
265 const base::FilePath& path, | |
266 bool is_directory) { | |
267 GrantedFileEntry result; | |
268 storage::IsolatedContext* isolated_context = | |
269 storage::IsolatedContext::GetInstance(); | |
270 DCHECK(isolated_context); | |
271 | |
272 result.filesystem_id = isolated_context->RegisterFileSystemForPath( | |
273 storage::kFileSystemTypeNativeForPlatformApp, std::string(), path, | |
274 &result.registered_name); | |
275 | |
276 content::ChildProcessSecurityPolicy* policy = | |
277 content::ChildProcessSecurityPolicy::GetInstance(); | |
278 policy->GrantReadFileSystem(renderer_id, result.filesystem_id); | |
279 if (HasFileSystemWritePermission(extension)) { | |
280 if (is_directory) { | |
281 policy->GrantCreateReadWriteFileSystem(renderer_id, result.filesystem_id); | |
282 } else { | |
283 policy->GrantWriteFileSystem(renderer_id, result.filesystem_id); | |
284 policy->GrantDeleteFromFileSystem(renderer_id, result.filesystem_id); | |
285 } | |
286 } | |
287 | |
288 result.id = result.filesystem_id + ":" + result.registered_name; | |
289 return result; | |
290 } | |
291 | |
292 void PrepareFilesForWritableApp( | |
293 const std::vector<base::FilePath>& paths, | |
294 Profile* profile, | |
295 const std::set<base::FilePath>& directory_paths, | |
296 const base::Closure& on_success, | |
297 const base::Callback<void(const base::FilePath&)>& on_failure) { | |
298 scoped_refptr<WritableFileChecker> checker(new WritableFileChecker( | |
299 paths, profile, directory_paths, on_success, on_failure)); | |
300 checker->Check(); | |
301 } | |
302 | |
303 bool HasFileSystemWritePermission(const Extension* extension) { | |
304 return extension->permissions_data()->HasAPIPermission( | |
305 APIPermission::kFileSystemWrite); | |
306 } | |
307 | |
308 bool ValidateFileEntryAndGetPath(const std::string& filesystem_name, | |
309 const std::string& filesystem_path, | |
310 int render_process_id, | |
311 base::FilePath* file_path, | |
312 std::string* error) { | |
313 if (filesystem_path.empty()) { | |
314 *error = kInvalidParameters; | |
315 return false; | |
316 } | |
317 | |
318 std::string filesystem_id; | |
319 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) { | |
320 *error = kInvalidParameters; | |
321 return false; | |
322 } | |
323 | |
324 // Only return the display path if the process has read access to the | |
325 // filesystem. | |
326 content::ChildProcessSecurityPolicy* policy = | |
327 content::ChildProcessSecurityPolicy::GetInstance(); | |
328 if (!policy->CanReadFileSystem(render_process_id, filesystem_id)) { | |
329 *error = kSecurityError; | |
330 return false; | |
331 } | |
332 | |
333 storage::IsolatedContext* context = storage::IsolatedContext::GetInstance(); | |
334 base::FilePath relative_path = | |
335 base::FilePath::FromUTF8Unsafe(filesystem_path); | |
336 base::FilePath virtual_path = | |
337 context->CreateVirtualRootPath(filesystem_id).Append(relative_path); | |
338 storage::FileSystemType type; | |
339 storage::FileSystemMountOption mount_option; | |
340 std::string cracked_id; | |
341 if (!context->CrackVirtualPath(virtual_path, &filesystem_id, &type, | |
342 &cracked_id, file_path, &mount_option)) { | |
343 *error = kInvalidParameters; | |
344 return false; | |
345 } | |
346 | |
347 // The file system API is only intended to operate on file entries that | |
348 // correspond to a native file, selected by the user so only allow file | |
349 // systems returned by the file system API or from a drag and drop operation. | |
350 if (type != storage::kFileSystemTypeNativeForPlatformApp && | |
351 type != storage::kFileSystemTypeDragged) { | |
352 *error = kInvalidParameters; | |
353 return false; | |
354 } | |
355 | |
356 return true; | |
357 } | |
358 | |
359 } // namespace app_file_handler_util | |
360 | |
361 } // namespace extensions | |
OLD | NEW |