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