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 "extensions/browser/api/file_handlers/app_file_handler_util.h"
6
7 #include <set>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/files/file.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/memory/scoped_refptr.h"
15 #include "base/task/post_task.h"
16 #include "base/task/task_traits.h"
17 #include "base/task/thread_pool.h"
18 #include "build/build_config.h"
19 #include "build/chromeos_buildflags.h"
20 #include "components/services/app_service/public/cpp/file_handler.h"
21 #include "components/services/app_service/public/cpp/file_handler_info.h"
22 #include "content/public/browser/browser_context.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/child_process_security_policy.h"
25 #include "extensions/browser/api/extensions_api_client.h"
26 #include "extensions/browser/entry_info.h"
27 #include "extensions/browser/extension_prefs.h"
28 #include "extensions/browser/granted_file_entry.h"
29 #include "extensions/common/permissions/permissions_data.h"
30 #include "net/base/mime_util.h"
31 #include "storage/browser/file_system/isolated_context.h"
32 #include "storage/common/file_system/file_system_mount_option.h"
33 #include "storage/common/file_system/file_system_types.h"
34
35 #if BUILDFLAG(IS_CHROMEOS_ASH)
36 #include "extensions/browser/api/file_handlers/non_native_file_system_delegate.h"
37 #endif
38
39 namespace extensions {
40
41 namespace app_file_handler_util {
42
43 const char kInvalidParameters[] = "Invalid parameters";
44 const char kSecurityError[] = "Security error";
45
46 namespace {
47
FileHandlerCanHandleFileWithExtension(const apps::FileHandlerInfo & handler,const base::FilePath & path)48 bool FileHandlerCanHandleFileWithExtension(const apps::FileHandlerInfo& handler,
49 const base::FilePath& path) {
50 for (auto extension = handler.extensions.cbegin();
51 extension != handler.extensions.cend(); ++extension) {
52 if (*extension == "*")
53 return true;
54
55 // Accept files whose extension or combined extension (e.g. ".tar.gz")
56 // match the supported extensions of file handler.
57 base::FilePath::StringType handler_extention(
58 base::FilePath::kExtensionSeparator +
59 base::FilePath::FromUTF8Unsafe(*extension).value());
60 if (base::FilePath::CompareEqualIgnoreCase(handler_extention,
61 path.Extension()) ||
62 base::FilePath::CompareEqualIgnoreCase(handler_extention,
63 path.FinalExtension())) {
64 return true;
65 }
66
67 // Also accept files with no extension for handlers that support an
68 // empty extension, i.e. both "foo" and "foo." match.
69 if (extension->empty() &&
70 path.MatchesExtension(base::FilePath::StringType())) {
71 return true;
72 }
73 }
74 return false;
75 }
76
FileHandlerCanHandleFileWithMimeType(const apps::FileHandlerInfo & handler,const std::string & mime_type)77 bool FileHandlerCanHandleFileWithMimeType(const apps::FileHandlerInfo& handler,
78 const std::string& mime_type) {
79 for (auto type = handler.types.cbegin(); type != handler.types.cend();
80 ++type) {
81 if (net::MatchesMimeType(*type, mime_type))
82 return true;
83 }
84 return false;
85 }
86
WebAppFileHandlerCanHandleFileWithExtension(const apps::FileHandler & file_handler,const base::FilePath & path)87 bool WebAppFileHandlerCanHandleFileWithExtension(
88 const apps::FileHandler& file_handler,
89 const base::FilePath& path) {
90 // Build a list of file extensions supported by the handler.
91 //
92 // TODO(crbug.com/938103): Duplicates functionality from
93 // FileHandlerManager::GetFileExtensionsFromFileHandlers.
94 std::set<std::string> file_extensions;
95 for (const auto& accept_entry : file_handler.accept)
96 file_extensions.insert(accept_entry.file_extensions.begin(),
97 accept_entry.file_extensions.end());
98
99 for (const auto& file_extension : file_extensions) {
100 if (file_extension == "*")
101 return true;
102
103 // Accept files whose extensions or combined extensions (e.g. ".tar.gz")
104 // match the supported extensions of the file handler.
105 base::FilePath::StringType file_extension_stringtype(
106 base::FilePath::FromUTF8Unsafe(file_extension).value());
107 if (base::FilePath::CompareEqualIgnoreCase(file_extension_stringtype,
108 path.Extension()) ||
109 base::FilePath::CompareEqualIgnoreCase(file_extension_stringtype,
110 path.FinalExtension()))
111 return true;
112 }
113 return false;
114 }
115
WebAppFileHandlerCanHandleFileWithMimeType(const apps::FileHandler & file_handler,const std::string & mime_type)116 bool WebAppFileHandlerCanHandleFileWithMimeType(
117 const apps::FileHandler& file_handler,
118 const std::string& mime_type) {
119 for (const auto& accept_entry : file_handler.accept) {
120 if (net::MatchesMimeType(accept_entry.mime_type, mime_type))
121 return true;
122 }
123 return false;
124 }
125
PrepareNativeLocalFileForWritableApp(const base::FilePath & path,bool is_directory)126 bool PrepareNativeLocalFileForWritableApp(const base::FilePath& path,
127 bool is_directory) {
128 // Don't allow links.
129 if (base::PathExists(path) && base::IsLink(path))
130 return false;
131
132 if (is_directory)
133 return base::DirectoryExists(path);
134
135 // Create the file if it doesn't already exist.
136 int creation_flags = base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ;
137 base::File file(path, creation_flags);
138
139 return file.IsValid();
140 }
141
142 // Checks whether a list of paths are all OK for writing and calls a provided
143 // on_success or on_failure callback when done. A path is OK for writing if it
144 // is not a symlink, is not in a blocklisted path and can be opened for writing.
145 // Creates files if they do not exist, but fails for non-existent directory
146 // paths. On Chrome OS, also fails for non-local files that don't already exist.
147 class WritableFileChecker
148 : public base::RefCountedThreadSafe<WritableFileChecker> {
149 public:
150 WritableFileChecker(
151 const std::vector<base::FilePath>& paths,
152 content::BrowserContext* context,
153 const std::set<base::FilePath>& directory_paths,
154 base::OnceClosure on_success,
155 base::OnceCallback<void(const base::FilePath&)> on_failure);
156
157 void Check();
158
159 private:
160 friend class base::RefCountedThreadSafe<WritableFileChecker>;
161 virtual ~WritableFileChecker();
162
163 // Called when a work item is completed. If all work items are done, this
164 // calls the success or failure callback.
165 void TaskDone();
166
167 // Reports an error in completing a work item. This may be called more than
168 // once, but only the last message will be retained.
169 void Error(const base::FilePath& error_path);
170
171 void CheckLocalWritableFiles();
172
173 // Called when processing a file is completed with either a success or an
174 // error.
175 void OnPrepareFileDone(const base::FilePath& path, bool success);
176
177 const std::vector<base::FilePath> paths_;
178 content::BrowserContext* context_;
179 const std::set<base::FilePath> directory_paths_;
180 size_t outstanding_tasks_;
181 base::FilePath error_path_;
182 base::OnceClosure on_success_;
183 base::OnceCallback<void(const base::FilePath&)> on_failure_;
184 };
185
WritableFileChecker(const std::vector<base::FilePath> & paths,content::BrowserContext * context,const std::set<base::FilePath> & directory_paths,base::OnceClosure on_success,base::OnceCallback<void (const base::FilePath &)> on_failure)186 WritableFileChecker::WritableFileChecker(
187 const std::vector<base::FilePath>& paths,
188 content::BrowserContext* context,
189 const std::set<base::FilePath>& directory_paths,
190 base::OnceClosure on_success,
191 base::OnceCallback<void(const base::FilePath&)> on_failure)
192 : paths_(paths),
193 context_(context),
194 directory_paths_(directory_paths),
195 outstanding_tasks_(1),
196 on_success_(std::move(on_success)),
197 on_failure_(std::move(on_failure)) {}
198
Check()199 void WritableFileChecker::Check() {
200 outstanding_tasks_ = paths_.size();
201 for (const auto& path : paths_) {
202 bool is_directory = directory_paths_.find(path) != directory_paths_.end();
203 #if BUILDFLAG(IS_CHROMEOS_ASH)
204 NonNativeFileSystemDelegate* delegate =
205 ExtensionsAPIClient::Get()->GetNonNativeFileSystemDelegate();
206 if (delegate && delegate->IsUnderNonNativeLocalPath(context_, path)) {
207 if (is_directory) {
208 delegate->IsNonNativeLocalPathDirectory(
209 context_, path,
210 base::BindOnce(&WritableFileChecker::OnPrepareFileDone, this,
211 path));
212 } else {
213 delegate->PrepareNonNativeLocalFileForWritableApp(
214 context_, path,
215 base::BindOnce(&WritableFileChecker::OnPrepareFileDone, this,
216 path));
217 }
218 continue;
219 }
220 #endif
221 base::ThreadPool::PostTaskAndReplyWithResult(
222 FROM_HERE, {base::TaskPriority::USER_BLOCKING, base::MayBlock()},
223 base::BindOnce(&PrepareNativeLocalFileForWritableApp, path,
224 is_directory),
225 base::BindOnce(&WritableFileChecker::OnPrepareFileDone, this, path));
226 }
227 }
228
~WritableFileChecker()229 WritableFileChecker::~WritableFileChecker() {}
230
TaskDone()231 void WritableFileChecker::TaskDone() {
232 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
233 if (--outstanding_tasks_ == 0) {
234 if (error_path_.empty())
235 std::move(on_success_).Run();
236 else
237 std::move(on_failure_).Run(error_path_);
238 on_success_.Reset();
239 on_failure_.Reset();
240 }
241 }
242
243 // Reports an error in completing a work item. This may be called more than
244 // once, but only the last message will be retained.
Error(const base::FilePath & error_path)245 void WritableFileChecker::Error(const base::FilePath& error_path) {
246 DCHECK(!error_path.empty());
247 error_path_ = error_path;
248 TaskDone();
249 }
250
OnPrepareFileDone(const base::FilePath & path,bool success)251 void WritableFileChecker::OnPrepareFileDone(const base::FilePath& path,
252 bool success) {
253 if (success)
254 TaskDone();
255 else
256 Error(path);
257 }
258
259 } // namespace
260
WebAppFileHandlerMatch(const apps::FileHandler * file_handler)261 WebAppFileHandlerMatch::WebAppFileHandlerMatch(
262 const apps::FileHandler* file_handler)
263 : file_handler_(file_handler) {}
264 WebAppFileHandlerMatch::~WebAppFileHandlerMatch() = default;
265
file_handler() const266 const apps::FileHandler& WebAppFileHandlerMatch::file_handler() const {
267 return *file_handler_;
268 }
269
matched_mime_type() const270 bool WebAppFileHandlerMatch::matched_mime_type() const {
271 return matched_mime_type_;
272 }
273
matched_file_extension() const274 bool WebAppFileHandlerMatch::matched_file_extension() const {
275 return matched_file_extension_;
276 }
277
DoMatch(const EntryInfo & entry)278 bool WebAppFileHandlerMatch::DoMatch(const EntryInfo& entry) {
279 // TODO(crbug.com/1060026): At the moment, apps::FileHandler doesn't have
280 // an include_directories flag. It may be necessary to add one as this new
281 // representation replaces apps::FileHandlerInfo.
282 if (entry.is_directory)
283 return false;
284
285 if (WebAppFileHandlerCanHandleFileWithMimeType(*file_handler_,
286 entry.mime_type)) {
287 matched_mime_type_ = true;
288 return true;
289 }
290
291 if (WebAppFileHandlerCanHandleFileWithExtension(*file_handler_, entry.path)) {
292 matched_file_extension_ = true;
293 return true;
294 }
295
296 return false;
297 }
298
FileHandlerForId(const Extension & app,const std::string & handler_id)299 const apps::FileHandlerInfo* FileHandlerForId(const Extension& app,
300 const std::string& handler_id) {
301 const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app);
302 if (!file_handlers)
303 return NULL;
304
305 for (auto i = file_handlers->cbegin(); i != file_handlers->cend(); i++) {
306 if (i->id == handler_id)
307 return &*i;
308 }
309 return NULL;
310 }
311
FindFileHandlerMatchesForEntries(const Extension & app,const std::vector<EntryInfo> & entries)312 std::vector<FileHandlerMatch> FindFileHandlerMatchesForEntries(
313 const Extension& app,
314 const std::vector<EntryInfo>& entries) {
315 if (entries.empty())
316 return std::vector<FileHandlerMatch>();
317
318 // Look for file handlers which can handle all the MIME types
319 // or file name extensions specified.
320 const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app);
321 if (!file_handlers)
322 return std::vector<FileHandlerMatch>();
323
324 return MatchesFromFileHandlersForEntries(*file_handlers, entries);
325 }
326
MatchesFromFileHandlersForEntries(const FileHandlersInfo & file_handlers,const std::vector<EntryInfo> & entries)327 std::vector<FileHandlerMatch> MatchesFromFileHandlersForEntries(
328 const FileHandlersInfo& file_handlers,
329 const std::vector<EntryInfo>& entries) {
330 std::vector<FileHandlerMatch> matches;
331
332 for (const apps::FileHandlerInfo& handler : file_handlers) {
333 bool handles_all_types = true;
334 FileHandlerMatch match;
335
336 // Lifetime of the handler should be the same as usage of the matches
337 // so the pointer shouldn't end up stale.
338 match.handler = &handler;
339 match.matched_mime = match.matched_file_extension = false;
340 for (const auto& entry : entries) {
341 if (entry.is_directory) {
342 if (!handler.include_directories) {
343 handles_all_types = false;
344 break;
345 }
346 } else {
347 match.matched_mime =
348 FileHandlerCanHandleFileWithMimeType(handler, entry.mime_type);
349 if (!match.matched_mime) {
350 match.matched_file_extension =
351 FileHandlerCanHandleFileWithExtension(handler, entry.path);
352 if (!match.matched_file_extension) {
353 handles_all_types = false;
354 break;
355 }
356 }
357 }
358 }
359 if (handles_all_types) {
360 matches.push_back(match);
361 }
362 }
363 return matches;
364 }
365
MatchesFromWebAppFileHandlersForEntries(const apps::FileHandlers & file_handlers,const std::vector<EntryInfo> & entries)366 std::vector<WebAppFileHandlerMatch> MatchesFromWebAppFileHandlersForEntries(
367 const apps::FileHandlers& file_handlers,
368 const std::vector<EntryInfo>& entries) {
369 std::vector<WebAppFileHandlerMatch> matches;
370
371 for (const auto& file_handler : file_handlers) {
372 bool handles_all_types = true;
373
374 // The lifetime of the file handler should be the same as the usage of the
375 // matches, so the pointer shouldn't end up stale.
376 WebAppFileHandlerMatch match(&file_handler);
377
378 for (const auto& entry : entries) {
379 if (!match.DoMatch(entry)) {
380 handles_all_types = false;
381 break;
382 }
383 }
384
385 if (handles_all_types)
386 matches.push_back(match);
387 }
388
389 return matches;
390 }
391
FileHandlerCanHandleEntry(const apps::FileHandlerInfo & handler,const EntryInfo & entry)392 bool FileHandlerCanHandleEntry(const apps::FileHandlerInfo& handler,
393 const EntryInfo& entry) {
394 if (entry.is_directory)
395 return handler.include_directories;
396
397 return FileHandlerCanHandleFileWithMimeType(handler, entry.mime_type) ||
398 FileHandlerCanHandleFileWithExtension(handler, entry.path);
399 }
400
WebAppFileHandlerCanHandleEntry(const apps::FileHandler & handler,const EntryInfo & entry)401 bool WebAppFileHandlerCanHandleEntry(const apps::FileHandler& handler,
402 const EntryInfo& entry) {
403 // TODO(crbug.com/938103): At the moment, apps::FileHandler doesn't have an
404 // include_directories flag. It may be necessary to add one as this new
405 // representation replaces apps::FileHandlerInfo.
406 if (entry.is_directory)
407 return false;
408
409 return WebAppFileHandlerCanHandleFileWithMimeType(handler, entry.mime_type) ||
410 WebAppFileHandlerCanHandleFileWithExtension(handler, entry.path);
411 }
412
CreateFileEntry(content::BrowserContext * context,const Extension * extension,int renderer_id,const base::FilePath & path,bool is_directory)413 GrantedFileEntry CreateFileEntry(content::BrowserContext* context,
414 const Extension* extension,
415 int renderer_id,
416 const base::FilePath& path,
417 bool is_directory) {
418 GrantedFileEntry result;
419 storage::IsolatedContext* isolated_context =
420 storage::IsolatedContext::GetInstance();
421 DCHECK(isolated_context);
422
423 storage::IsolatedContext::ScopedFSHandle filesystem =
424 isolated_context->RegisterFileSystemForPath(
425 storage::kFileSystemTypeNativeForPlatformApp, std::string(), path,
426 &result.registered_name);
427 result.filesystem_id = filesystem.id();
428
429 content::ChildProcessSecurityPolicy* policy =
430 content::ChildProcessSecurityPolicy::GetInstance();
431 policy->GrantReadFileSystem(renderer_id, result.filesystem_id);
432 if (HasFileSystemWritePermission(extension)) {
433 if (is_directory) {
434 policy->GrantCreateReadWriteFileSystem(renderer_id, result.filesystem_id);
435 } else {
436 policy->GrantWriteFileSystem(renderer_id, result.filesystem_id);
437 policy->GrantDeleteFromFileSystem(renderer_id, result.filesystem_id);
438 }
439 }
440
441 result.id = result.filesystem_id + ":" + result.registered_name;
442 return result;
443 }
444
PrepareFilesForWritableApp(const std::vector<base::FilePath> & paths,content::BrowserContext * context,const std::set<base::FilePath> & directory_paths,base::OnceClosure on_success,base::OnceCallback<void (const base::FilePath &)> on_failure)445 void PrepareFilesForWritableApp(
446 const std::vector<base::FilePath>& paths,
447 content::BrowserContext* context,
448 const std::set<base::FilePath>& directory_paths,
449 base::OnceClosure on_success,
450 base::OnceCallback<void(const base::FilePath&)> on_failure) {
451 auto checker = base::MakeRefCounted<WritableFileChecker>(
452 paths, context, directory_paths, std::move(on_success),
453 std::move(on_failure));
454 checker->Check();
455 }
456
HasFileSystemWritePermission(const Extension * extension)457 bool HasFileSystemWritePermission(const Extension* extension) {
458 return extension->permissions_data()->HasAPIPermission(
459 APIPermission::kFileSystemWrite);
460 }
461
ValidateFileEntryAndGetPath(const std::string & filesystem_name,const std::string & filesystem_path,int render_process_id,base::FilePath * file_path,std::string * error)462 bool ValidateFileEntryAndGetPath(const std::string& filesystem_name,
463 const std::string& filesystem_path,
464 int render_process_id,
465 base::FilePath* file_path,
466 std::string* error) {
467 if (filesystem_path.empty()) {
468 *error = kInvalidParameters;
469 return false;
470 }
471
472 std::string filesystem_id;
473 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) {
474 *error = kInvalidParameters;
475 return false;
476 }
477
478 // Only return the display path if the process has read access to the
479 // filesystem.
480 content::ChildProcessSecurityPolicy* policy =
481 content::ChildProcessSecurityPolicy::GetInstance();
482 if (!policy->CanReadFileSystem(render_process_id, filesystem_id)) {
483 *error = kSecurityError;
484 return false;
485 }
486
487 storage::IsolatedContext* context = storage::IsolatedContext::GetInstance();
488 base::FilePath relative_path =
489 base::FilePath::FromUTF8Unsafe(filesystem_path);
490 base::FilePath virtual_path =
491 context->CreateVirtualRootPath(filesystem_id).Append(relative_path);
492 storage::FileSystemType type;
493 storage::FileSystemMountOption mount_option;
494 std::string cracked_id;
495 if (!context->CrackVirtualPath(virtual_path, &filesystem_id, &type,
496 &cracked_id, file_path, &mount_option)) {
497 *error = kInvalidParameters;
498 return false;
499 }
500
501 // The file system API is only intended to operate on file entries that
502 // correspond to a native file, selected by the user so only allow file
503 // systems returned by the file system API or from a drag and drop operation.
504 if (type != storage::kFileSystemTypeNativeForPlatformApp &&
505 type != storage::kFileSystemTypeDragged) {
506 *error = kInvalidParameters;
507 return false;
508 }
509
510 return true;
511 }
512
513 } // namespace app_file_handler_util
514
515 } // namespace extensions
516