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