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 |