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 "apps/browser/file_handler_util.h"
8 #include "base/file_util.h"
9 #include "base/files/file.h"
10 #include "base/files/file_path.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "content/public/browser/child_process_security_policy.h"
13 #include "content/public/browser/render_process_host.h"
14 #include "extensions/browser/extension_prefs.h"
15 #include "extensions/common/permissions/permissions_data.h"
16 #include "net/base/mime_util.h"
17 #include "webkit/browser/fileapi/isolated_context.h"
18 #include "webkit/common/fileapi/file_system_mount_option.h"
19 #include "webkit/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 using apps::file_handler_util::GrantedFileEntry;
26
27 namespace extensions {
28
29 namespace app_file_handler_util {
30
31 const char kInvalidParameters[] = "Invalid parameters";
32 const char kSecurityError[] = "Security error";
33
34 namespace {
35
FileHandlerCanHandleFileWithExtension(const FileHandlerInfo & handler,const base::FilePath & path)36 bool FileHandlerCanHandleFileWithExtension(
37 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 if (path.MatchesExtension(
46 base::FilePath::kExtensionSeparator +
47 base::FilePath::FromUTF8Unsafe(*extension).value())) {
48 return true;
49 }
50
51 // Also accept files with no extension for handlers that support an
52 // empty extension, i.e. both "foo" and "foo." match.
53 if (extension->empty() &&
54 path.MatchesExtension(base::FilePath::StringType())) {
55 return true;
56 }
57 }
58 return false;
59 }
60
FileHandlerCanHandleFileWithMimeType(const FileHandlerInfo & handler,const std::string & mime_type)61 bool FileHandlerCanHandleFileWithMimeType(
62 const FileHandlerInfo& handler,
63 const std::string& mime_type) {
64 for (std::set<std::string>::const_iterator type = handler.types.begin();
65 type != handler.types.end(); ++type) {
66 if (net::MatchesMimeType(*type, mime_type))
67 return true;
68 }
69 return false;
70 }
71
DoCheckWritableFile(const base::FilePath & path,bool is_directory)72 bool DoCheckWritableFile(const base::FilePath& path, bool is_directory) {
73 // Don't allow links.
74 if (base::PathExists(path) && base::IsLink(path))
75 return false;
76
77 if (is_directory)
78 return base::DirectoryExists(path);
79
80 // Create the file if it doesn't already exist.
81 int creation_flags = base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ;
82 base::File file(path, creation_flags);
83 return file.IsValid();
84 }
85
86 // Checks whether a list of paths are all OK for writing and calls a provided
87 // on_success or on_failure callback when done. A file is OK for writing if it
88 // is not a symlink, is not in a blacklisted path and can be opened for writing;
89 // files are created if they do not exist.
90 class WritableFileChecker
91 : public base::RefCountedThreadSafe<WritableFileChecker> {
92 public:
93 WritableFileChecker(
94 const std::vector<base::FilePath>& paths,
95 Profile* profile,
96 bool is_directory,
97 const base::Closure& on_success,
98 const base::Callback<void(const base::FilePath&)>& on_failure);
99
100 void Check();
101
102 private:
103 friend class base::RefCountedThreadSafe<WritableFileChecker>;
104 virtual ~WritableFileChecker();
105
106 // Called when a work item is completed. If all work items are done, this
107 // calls the success or failure callback.
108 void TaskDone();
109
110 // Reports an error in completing a work item. This may be called more than
111 // once, but only the last message will be retained.
112 void Error(const base::FilePath& error_path);
113
114 void CheckLocalWritableFiles();
115
116 #if defined(OS_CHROMEOS)
117 void NonNativeLocalPathCheckDone(const base::FilePath& path, bool success);
118 #endif
119
120 const std::vector<base::FilePath> paths_;
121 Profile* profile_;
122 const bool is_directory_;
123 int outstanding_tasks_;
124 base::FilePath error_path_;
125 base::Closure on_success_;
126 base::Callback<void(const base::FilePath&)> on_failure_;
127 };
128
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)129 WritableFileChecker::WritableFileChecker(
130 const std::vector<base::FilePath>& paths,
131 Profile* profile,
132 bool is_directory,
133 const base::Closure& on_success,
134 const base::Callback<void(const base::FilePath&)>& on_failure)
135 : paths_(paths),
136 profile_(profile),
137 is_directory_(is_directory),
138 outstanding_tasks_(1),
139 on_success_(on_success),
140 on_failure_(on_failure) {}
141
Check()142 void WritableFileChecker::Check() {
143 #if defined(OS_CHROMEOS)
144 if (file_manager::util::IsUnderNonNativeLocalPath(profile_, paths_[0])) {
145 outstanding_tasks_ = paths_.size();
146 for (std::vector<base::FilePath>::const_iterator it = paths_.begin();
147 it != paths_.end();
148 ++it) {
149 if (is_directory_) {
150 file_manager::util::IsNonNativeLocalPathDirectory(
151 profile_,
152 *it,
153 base::Bind(&WritableFileChecker::NonNativeLocalPathCheckDone,
154 this, *it));
155 } else {
156 file_manager::util::PrepareNonNativeLocalFileForWritableApp(
157 profile_,
158 *it,
159 base::Bind(&WritableFileChecker::NonNativeLocalPathCheckDone,
160 this, *it));
161 }
162 }
163 return;
164 }
165 #endif
166 content::BrowserThread::PostTask(
167 content::BrowserThread::FILE,
168 FROM_HERE,
169 base::Bind(&WritableFileChecker::CheckLocalWritableFiles, this));
170 }
171
~WritableFileChecker()172 WritableFileChecker::~WritableFileChecker() {}
173
TaskDone()174 void WritableFileChecker::TaskDone() {
175 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
176 if (--outstanding_tasks_ == 0) {
177 if (error_path_.empty())
178 on_success_.Run();
179 else
180 on_failure_.Run(error_path_);
181 }
182 }
183
184 // Reports an error in completing a work item. This may be called more than
185 // once, but only the last message will be retained.
Error(const base::FilePath & error_path)186 void WritableFileChecker::Error(const base::FilePath& error_path) {
187 DCHECK(!error_path.empty());
188 error_path_ = error_path;
189 TaskDone();
190 }
191
CheckLocalWritableFiles()192 void WritableFileChecker::CheckLocalWritableFiles() {
193 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
194 std::string error;
195 for (std::vector<base::FilePath>::const_iterator it = paths_.begin();
196 it != paths_.end();
197 ++it) {
198 if (!DoCheckWritableFile(*it, is_directory_)) {
199 content::BrowserThread::PostTask(
200 content::BrowserThread::UI,
201 FROM_HERE,
202 base::Bind(&WritableFileChecker::Error, this, *it));
203 return;
204 }
205 }
206 content::BrowserThread::PostTask(
207 content::BrowserThread::UI,
208 FROM_HERE,
209 base::Bind(&WritableFileChecker::TaskDone, this));
210 }
211
212 #if defined(OS_CHROMEOS)
NonNativeLocalPathCheckDone(const base::FilePath & path,bool success)213 void WritableFileChecker::NonNativeLocalPathCheckDone(
214 const base::FilePath& path,
215 bool success) {
216 if (success)
217 TaskDone();
218 else
219 Error(path);
220 }
221 #endif
222
223 } // namespace
224
FileHandlerForId(const Extension & app,const std::string & handler_id)225 const FileHandlerInfo* FileHandlerForId(const Extension& app,
226 const std::string& handler_id) {
227 const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app);
228 if (!file_handlers)
229 return NULL;
230
231 for (FileHandlersInfo::const_iterator i = file_handlers->begin();
232 i != file_handlers->end(); i++) {
233 if (i->id == handler_id)
234 return &*i;
235 }
236 return NULL;
237 }
238
FirstFileHandlerForFile(const Extension & app,const std::string & mime_type,const base::FilePath & path)239 const FileHandlerInfo* FirstFileHandlerForFile(
240 const Extension& app,
241 const std::string& mime_type,
242 const base::FilePath& path) {
243 const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app);
244 if (!file_handlers)
245 return NULL;
246
247 for (FileHandlersInfo::const_iterator i = file_handlers->begin();
248 i != file_handlers->end(); i++) {
249 if (FileHandlerCanHandleFile(*i, mime_type, path))
250 return &*i;
251 }
252 return NULL;
253 }
254
FindFileHandlersForFiles(const Extension & app,const PathAndMimeTypeSet & files)255 std::vector<const FileHandlerInfo*> FindFileHandlersForFiles(
256 const Extension& app, const PathAndMimeTypeSet& files) {
257 std::vector<const FileHandlerInfo*> handlers;
258 if (files.empty())
259 return handlers;
260
261 // Look for file handlers which can handle all the MIME types specified.
262 const FileHandlersInfo* file_handlers = FileHandlers::GetFileHandlers(&app);
263 if (!file_handlers)
264 return handlers;
265
266 for (FileHandlersInfo::const_iterator data = file_handlers->begin();
267 data != file_handlers->end(); ++data) {
268 bool handles_all_types = true;
269 for (PathAndMimeTypeSet::const_iterator it = files.begin();
270 it != files.end(); ++it) {
271 if (!FileHandlerCanHandleFile(*data, it->second, it->first)) {
272 handles_all_types = false;
273 break;
274 }
275 }
276 if (handles_all_types)
277 handlers.push_back(&*data);
278 }
279 return handlers;
280 }
281
FileHandlerCanHandleFile(const FileHandlerInfo & handler,const std::string & mime_type,const base::FilePath & path)282 bool FileHandlerCanHandleFile(
283 const FileHandlerInfo& handler,
284 const std::string& mime_type,
285 const base::FilePath& path) {
286 return FileHandlerCanHandleFileWithMimeType(handler, mime_type) ||
287 FileHandlerCanHandleFileWithExtension(handler, path);
288 }
289
CreateFileEntry(Profile * profile,const Extension * extension,int renderer_id,const base::FilePath & path,bool is_directory)290 GrantedFileEntry CreateFileEntry(
291 Profile* profile,
292 const Extension* extension,
293 int renderer_id,
294 const base::FilePath& path,
295 bool is_directory) {
296 GrantedFileEntry result;
297 fileapi::IsolatedContext* isolated_context =
298 fileapi::IsolatedContext::GetInstance();
299 DCHECK(isolated_context);
300
301 result.filesystem_id = isolated_context->RegisterFileSystemForPath(
302 fileapi::kFileSystemTypeNativeForPlatformApp, std::string(), 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 (!fileapi::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 fileapi::IsolatedContext* context = fileapi::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 fileapi::FileSystemType type;
370 fileapi::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 != fileapi::kFileSystemTypeNativeForPlatformApp &&
383 type != fileapi::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