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 "content/renderer/pepper/ppb_file_ref_impl.h" 6 7 #include "base/files/file_util_proxy.h" 8 #include "base/platform_file.h" 9 #include "base/strings/string_util.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "content/child/fileapi/file_system_dispatcher.h" 12 #include "content/common/view_messages.h" 13 #include "content/renderer/pepper/common.h" 14 #include "content/renderer/pepper/pepper_file_system_host.h" 15 #include "content/renderer/pepper/plugin_module.h" 16 #include "content/renderer/pepper/renderer_ppapi_host_impl.h" 17 #include "content/renderer/render_thread_impl.h" 18 #include "net/base/escape.h" 19 #include "ppapi/c/pp_errors.h" 20 #include "ppapi/c/ppb_file_io.h" 21 #include "ppapi/host/ppapi_host.h" 22 #include "ppapi/shared_impl/file_type_conversion.h" 23 #include "ppapi/shared_impl/time_conversion.h" 24 #include "ppapi/shared_impl/var.h" 25 #include "ppapi/thunk/enter.h" 26 #include "ppapi/thunk/ppb_file_system_api.h" 27 #include "url/gurl.h" 28 #include "webkit/common/fileapi/directory_entry.h" 29 #include "webkit/common/fileapi/file_system_util.h" 30 31 using ppapi::HostResource; 32 using ppapi::PPB_FileRef_CreateInfo; 33 using ppapi::PPTimeToTime; 34 using ppapi::StringVar; 35 using ppapi::TrackedCallback; 36 using ppapi::thunk::EnterResourceNoLock; 37 using ppapi::thunk::PPB_FileRef_API; 38 using ppapi::thunk::PPB_FileSystem_API; 39 40 namespace content { 41 42 namespace { 43 44 bool IsValidLocalPath(const std::string& path) { 45 // The path must start with '/' 46 if (path.empty() || path[0] != '/') 47 return false; 48 49 // The path must contain valid UTF-8 characters. 50 if (!IsStringUTF8(path)) 51 return false; 52 53 #if defined(OS_WIN) 54 base::FilePath::StringType path_win(path.begin(), path.end()); 55 base::FilePath file_path(path_win); 56 #else 57 base::FilePath file_path(path); 58 #endif 59 if (file_path.ReferencesParent()) 60 return false; 61 62 return true; 63 } 64 65 void TrimTrailingSlash(std::string* path) { 66 // If this path ends with a slash, then normalize it away unless path is the 67 // root path. 68 if (path->size() > 1 && path->at(path->size() - 1) == '/') 69 path->erase(path->size() - 1, 1); 70 } 71 72 std::string GetNameForExternalFilePath(const base::FilePath& in_path) { 73 const base::FilePath::StringType& path = in_path.value(); 74 size_t pos = path.rfind(base::FilePath::kSeparators[0]); 75 CHECK(pos != base::FilePath::StringType::npos); 76 #if defined(OS_WIN) 77 return WideToUTF8(path.substr(pos + 1)); 78 #elif defined(OS_POSIX) 79 return path.substr(pos + 1); 80 #else 81 #error "Unsupported platform." 82 #endif 83 } 84 85 std::string GetNameForVirtualFilePath(const std::string& path) { 86 if (path.size() == 1 && path[0] == '/') 87 return path; 88 89 // There should always be a leading slash at least! 90 size_t pos = path.rfind('/'); 91 CHECK(pos != std::string::npos); 92 return path.substr(pos + 1); 93 } 94 95 void IgnoreCloseCallback(base::PlatformFileError error_code) { 96 } 97 98 void PlatformFileInfoToPPFileInfo( 99 const base::PlatformFileInfo& file_info, 100 PP_FileSystemType file_system_type, 101 PP_FileInfo* info) { 102 DCHECK(info); 103 ppapi::PlatformFileInfoToPepperFileInfo(file_info, file_system_type, info); 104 } 105 106 void GetFileInfoCallback( 107 scoped_refptr<base::TaskRunner> task_runner, 108 base::PlatformFile file, 109 linked_ptr<PP_FileInfo> info, 110 scoped_refptr<TrackedCallback> callback, 111 base::PlatformFileError error_code, 112 const base::PlatformFileInfo& file_info) { 113 base::FileUtilProxy::Close( 114 task_runner.get(), file, base::Bind(&IgnoreCloseCallback)); 115 116 if (!TrackedCallback::IsPending(callback)) 117 return; 118 119 int32_t pp_error = ppapi::PlatformFileErrorToPepperError(error_code); 120 if (pp_error != PP_OK) { 121 callback->Run(pp_error); 122 return; 123 } 124 125 PlatformFileInfoToPPFileInfo( 126 file_info, PP_FILESYSTEMTYPE_EXTERNAL, info.get()); 127 128 callback->Run(PP_OK); 129 } 130 131 void QueryCallback(scoped_refptr<base::TaskRunner> task_runner, 132 linked_ptr<PP_FileInfo> info, 133 scoped_refptr<TrackedCallback> callback, 134 base::PlatformFileError error_code, 135 base::PassPlatformFile passed_file) { 136 if (!TrackedCallback::IsPending(callback)) 137 return; 138 139 int32_t pp_error = ppapi::PlatformFileErrorToPepperError(error_code); 140 if (pp_error != PP_OK) { 141 callback->Run(pp_error); 142 return; 143 } 144 base::PlatformFile file = passed_file.ReleaseValue(); 145 146 if (!base::FileUtilProxy::GetFileInfoFromPlatformFile( 147 task_runner.get(), 148 file, 149 base::Bind( 150 &GetFileInfoCallback, task_runner, file, info, callback))) { 151 base::FileUtilProxy::Close( 152 task_runner.get(), file, base::Bind(&IgnoreCloseCallback)); 153 callback->Run(PP_ERROR_FAILED); 154 } 155 } 156 157 void DidReadMetadata( 158 scoped_refptr<ppapi::TrackedCallback> callback, 159 linked_ptr<PP_FileInfo> info, 160 PP_FileSystemType file_system_type, 161 const base::PlatformFileInfo& file_info) { 162 if (!TrackedCallback::IsPending(callback)) 163 return; 164 165 PlatformFileInfoToPPFileInfo(file_info, file_system_type, info.get()); 166 callback->Run(PP_OK); 167 } 168 169 void DidReadDirectory( 170 scoped_refptr<ppapi::TrackedCallback> callback, 171 PPB_FileRef_Impl* dir_ref, 172 linked_ptr<std::vector<ppapi::PPB_FileRef_CreateInfo> > dir_files, 173 linked_ptr<std::vector<PP_FileType> > dir_file_types, 174 const std::vector<fileapi::DirectoryEntry>& entries, 175 bool has_more) { 176 if (!TrackedCallback::IsPending(callback)) 177 return; 178 179 // The current filesystem backend always returns false. 180 DCHECK(!has_more); 181 182 DCHECK(dir_ref); 183 DCHECK(dir_files.get()); 184 DCHECK(dir_file_types.get()); 185 186 std::string dir_path = dir_ref->GetCreateInfo().path; 187 if (dir_path.empty() || dir_path[dir_path.size() - 1] != '/') 188 dir_path += '/'; 189 190 for (size_t i = 0; i < entries.size(); ++i) { 191 const fileapi::DirectoryEntry& entry = entries[i]; 192 scoped_refptr<PPB_FileRef_Impl> file_ref(PPB_FileRef_Impl::CreateInternal( 193 dir_ref->pp_instance(), 194 dir_ref->file_system_resource(), 195 dir_path + fileapi::FilePathToString(base::FilePath(entry.name)))); 196 dir_files->push_back(file_ref->GetCreateInfo()); 197 dir_file_types->push_back( 198 entry.is_directory ? PP_FILETYPE_DIRECTORY : PP_FILETYPE_REGULAR); 199 // Add a ref count on behalf of the plugin side. 200 file_ref->GetReference(); 201 } 202 CHECK_EQ(dir_files->size(), dir_file_types->size()); 203 204 callback->Run(PP_OK); 205 } 206 207 void DidFinishFileOperation( 208 scoped_refptr<ppapi::TrackedCallback> callback, 209 base::PlatformFileError error_code) { 210 if (callback->completed()) 211 return; 212 callback->Run(ppapi::PlatformFileErrorToPepperError(error_code)); 213 } 214 215 } // namespace 216 217 PPB_FileRef_Impl::PPB_FileRef_Impl(const PPB_FileRef_CreateInfo& info, 218 PP_Resource file_system) 219 : PPB_FileRef_Shared(ppapi::OBJECT_IS_IMPL, info), 220 file_system_(file_system), 221 external_file_system_path_(), 222 routing_id_(MSG_ROUTING_NONE) { 223 if (RenderThreadImpl::current()) { // NULL in tests. 224 routing_id_ = RenderThreadImpl::current()->GenerateRoutingID(); 225 ChildThread::current()->AddRoute(routing_id_, this); 226 } 227 } 228 229 PPB_FileRef_Impl::PPB_FileRef_Impl(const PPB_FileRef_CreateInfo& info, 230 const base::FilePath& external_file_path) 231 : PPB_FileRef_Shared(ppapi::OBJECT_IS_IMPL, info), 232 file_system_(), 233 external_file_system_path_(external_file_path), 234 routing_id_(MSG_ROUTING_NONE) { 235 if (RenderThreadImpl::current()) { // NULL in tests. 236 routing_id_ = RenderThreadImpl::current()->GenerateRoutingID(); 237 ChildThread::current()->AddRoute(routing_id_, this); 238 } 239 } 240 241 PPB_FileRef_Impl::~PPB_FileRef_Impl() { 242 if (RenderThreadImpl::current()) 243 ChildThread::current()->RemoveRoute(routing_id_); 244 } 245 246 // static 247 PPB_FileRef_Impl* PPB_FileRef_Impl::CreateInternal(PP_Instance instance, 248 PP_Resource pp_file_system, 249 const std::string& path) { 250 PepperFileSystemHost* fs_host = GetFileSystemHostInternal( 251 instance, pp_file_system); 252 if (!fs_host) 253 return 0; 254 255 PP_FileSystemType type = fs_host->GetType(); 256 if (type != PP_FILESYSTEMTYPE_LOCALPERSISTENT && 257 type != PP_FILESYSTEMTYPE_LOCALTEMPORARY && 258 type != PP_FILESYSTEMTYPE_EXTERNAL && 259 type != PP_FILESYSTEMTYPE_ISOLATED) 260 return 0; 261 262 PPB_FileRef_CreateInfo info; 263 info.resource = HostResource::MakeInstanceOnly(instance); 264 info.file_system_plugin_resource = pp_file_system; 265 info.file_system_type = type; 266 267 // Validate the path. 268 info.path = path; 269 if (!IsValidLocalPath(info.path)) 270 return 0; 271 TrimTrailingSlash(&info.path); 272 273 info.name = GetNameForVirtualFilePath(info.path); 274 275 PPB_FileRef_Impl* file_ref = new PPB_FileRef_Impl(info, pp_file_system); 276 277 RendererPpapiHostImpl* renderer_host = 278 RendererPpapiHostImpl::GetForPPInstance(instance); 279 if (renderer_host && renderer_host->IsRunningInProcess()) 280 file_ref->AddFileSystemRefCount(); 281 return file_ref; 282 } 283 284 // static 285 PPB_FileRef_Impl* PPB_FileRef_Impl::CreateExternal( 286 PP_Instance instance, 287 const base::FilePath& external_file_path, 288 const std::string& display_name) { 289 PPB_FileRef_CreateInfo info; 290 info.resource = HostResource::MakeInstanceOnly(instance); 291 info.file_system_plugin_resource = 0; 292 info.file_system_type = PP_FILESYSTEMTYPE_EXTERNAL; 293 if (display_name.empty()) 294 info.name = GetNameForExternalFilePath(external_file_path); 295 else 296 info.name = display_name; 297 298 return new PPB_FileRef_Impl(info, external_file_path); 299 } 300 301 PP_Resource PPB_FileRef_Impl::GetParent() { 302 if (GetFileSystemType() == PP_FILESYSTEMTYPE_EXTERNAL) 303 return 0; 304 305 const std::string& virtual_path = GetCreateInfo().path; 306 307 // There should always be a leading slash at least! 308 size_t pos = virtual_path.rfind('/'); 309 CHECK(pos != std::string::npos); 310 311 // If the path is "/foo", then we want to include the slash. 312 if (pos == 0) 313 pos++; 314 std::string parent_path = virtual_path.substr(0, pos); 315 316 scoped_refptr<PPB_FileRef_Impl> parent_ref( 317 CreateInternal(pp_instance(), file_system_, parent_path)); 318 if (!parent_ref.get()) 319 return 0; 320 return parent_ref->GetReference(); 321 } 322 323 int32_t PPB_FileRef_Impl::MakeDirectory( 324 PP_Bool make_ancestors, 325 scoped_refptr<TrackedCallback> callback) { 326 if (!IsValidNonExternalFileSystem()) 327 return PP_ERROR_NOACCESS; 328 329 FileSystemDispatcher* file_system_dispatcher = 330 ChildThread::current()->file_system_dispatcher(); 331 file_system_dispatcher->CreateDirectory( 332 GetFileSystemURL(), false /* exclusive */, PP_ToBool(make_ancestors), 333 base::Bind(&DidFinishFileOperation, callback)); 334 return PP_OK_COMPLETIONPENDING; 335 } 336 337 int32_t PPB_FileRef_Impl::Touch(PP_Time last_access_time, 338 PP_Time last_modified_time, 339 scoped_refptr<TrackedCallback> callback) { 340 if (!IsValidNonExternalFileSystem()) 341 return PP_ERROR_NOACCESS; 342 343 FileSystemDispatcher* file_system_dispatcher = 344 ChildThread::current()->file_system_dispatcher(); 345 file_system_dispatcher->TouchFile( 346 GetFileSystemURL(), 347 PPTimeToTime(last_access_time), 348 PPTimeToTime(last_modified_time), 349 base::Bind(&DidFinishFileOperation, callback)); 350 return PP_OK_COMPLETIONPENDING; 351 } 352 353 int32_t PPB_FileRef_Impl::Delete(scoped_refptr<TrackedCallback> callback) { 354 if (!IsValidNonExternalFileSystem()) 355 return PP_ERROR_NOACCESS; 356 357 FileSystemDispatcher* file_system_dispatcher = 358 ChildThread::current()->file_system_dispatcher(); 359 file_system_dispatcher->Remove( 360 GetFileSystemURL(), 361 false /* recursive */, 362 base::Bind(&DidFinishFileOperation, callback)); 363 return PP_OK_COMPLETIONPENDING; 364 } 365 366 int32_t PPB_FileRef_Impl::Rename(PP_Resource new_pp_file_ref, 367 scoped_refptr<TrackedCallback> callback) { 368 EnterResourceNoLock<PPB_FileRef_API> enter(new_pp_file_ref, true); 369 if (enter.failed()) 370 return PP_ERROR_BADRESOURCE; 371 PPB_FileRef_Impl* new_file_ref = 372 static_cast<PPB_FileRef_Impl*>(enter.object()); 373 374 if (!IsValidNonExternalFileSystem() || 375 file_system_ != new_file_ref->file_system_) 376 return PP_ERROR_NOACCESS; 377 378 // TODO(viettrungluu): Also cancel when the new file ref is destroyed? 379 // http://crbug.com/67624 380 FileSystemDispatcher* file_system_dispatcher = 381 ChildThread::current()->file_system_dispatcher(); 382 file_system_dispatcher->Move( 383 GetFileSystemURL(), new_file_ref->GetFileSystemURL(), 384 base::Bind(&DidFinishFileOperation, callback)); 385 return PP_OK_COMPLETIONPENDING; 386 } 387 388 PP_Var PPB_FileRef_Impl::GetAbsolutePath() { 389 if (GetFileSystemType() != PP_FILESYSTEMTYPE_EXTERNAL) 390 return GetPath(); 391 if (!external_path_var_.get()) { 392 external_path_var_ = 393 new StringVar(external_file_system_path_.AsUTF8Unsafe()); 394 } 395 return external_path_var_->GetPPVar(); 396 } 397 398 base::FilePath PPB_FileRef_Impl::GetSystemPath() const { 399 if (GetFileSystemType() != PP_FILESYSTEMTYPE_EXTERNAL) { 400 NOTREACHED(); 401 return base::FilePath(); 402 } 403 return external_file_system_path_; 404 } 405 406 GURL PPB_FileRef_Impl::GetFileSystemURL() const { 407 if (GetFileSystemType() != PP_FILESYSTEMTYPE_LOCALPERSISTENT && 408 GetFileSystemType() != PP_FILESYSTEMTYPE_LOCALTEMPORARY && 409 GetFileSystemType() != PP_FILESYSTEMTYPE_EXTERNAL && 410 GetFileSystemType() != PP_FILESYSTEMTYPE_ISOLATED) { 411 NOTREACHED(); 412 return GURL(); 413 } 414 415 const std::string& virtual_path = GetCreateInfo().path; 416 CHECK(!virtual_path.empty()); // Should always be at least "/". 417 418 // Since |virtual_path_| starts with a '/', it looks like an absolute path. 419 // We need to trim off the '/' before calling Resolve, as FileSystem URLs 420 // start with a storage type identifier that looks like a path segment. 421 422 PepperFileSystemHost* host = GetFileSystemHost(); 423 if (!host) 424 return GURL(); 425 426 return GURL(host->GetRootUrl()).Resolve(net::EscapePath( 427 virtual_path.substr(1))); 428 } 429 430 bool PPB_FileRef_Impl::OnMessageReceived(const IPC::Message& msg) { 431 bool handled = true; 432 IPC_BEGIN_MESSAGE_MAP(PPB_FileRef_Impl, msg) 433 IPC_MESSAGE_HANDLER(ViewMsg_AsyncOpenPepperFile_ACK, OnAsyncFileOpened) 434 IPC_MESSAGE_UNHANDLED(handled = false) 435 IPC_END_MESSAGE_MAP() 436 return handled; 437 } 438 439 void PPB_FileRef_Impl::OnAsyncFileOpened( 440 base::PlatformFileError error_code, 441 IPC::PlatformFileForTransit file_for_transit, 442 int message_id) { 443 AsyncOpenFileCallback* callback = 444 pending_async_open_files_.Lookup(message_id); 445 DCHECK(callback); 446 pending_async_open_files_.Remove(message_id); 447 448 base::PlatformFile file = 449 IPC::PlatformFileForTransitToPlatformFile(file_for_transit); 450 callback->Run(error_code, base::PassPlatformFile(&file)); 451 // Make sure we won't leak file handle if the requester has died. 452 if (file != base::kInvalidPlatformFileValue) { 453 base::FileUtilProxy::Close( 454 RenderThreadImpl::current()->GetFileThreadMessageLoopProxy().get(), 455 file, 456 base::FileUtilProxy::StatusCallback()); 457 } 458 delete callback; 459 } 460 461 bool PPB_FileRef_Impl::IsValidNonExternalFileSystem() const { 462 PepperFileSystemHost* host = GetFileSystemHost(); 463 return HasValidFileSystem() && host && 464 host->GetType() != PP_FILESYSTEMTYPE_EXTERNAL; 465 } 466 467 bool PPB_FileRef_Impl::HasValidFileSystem() const { 468 PepperFileSystemHost* host = GetFileSystemHost(); 469 return host && host->IsOpened(); 470 } 471 472 int32_t PPB_FileRef_Impl::Query(PP_FileInfo* info, 473 scoped_refptr<TrackedCallback> callback) { 474 NOTREACHED(); 475 return PP_ERROR_FAILED; 476 } 477 478 int32_t PPB_FileRef_Impl::QueryInHost( 479 linked_ptr<PP_FileInfo> info, 480 scoped_refptr<TrackedCallback> callback) { 481 if (!file_system_) { 482 // External file system 483 // We have to do something totally different for external file systems. 484 485 // TODO(teravest): Use the SequencedWorkerPool instead. 486 scoped_refptr<base::TaskRunner> task_runner = 487 RenderThreadImpl::current()->GetFileThreadMessageLoopProxy(); 488 489 int message_id = pending_async_open_files_.Add(new AsyncOpenFileCallback( 490 base::Bind(&QueryCallback, task_runner, info, callback))); 491 RenderThreadImpl::current()->Send(new ViewHostMsg_AsyncOpenPepperFile( 492 routing_id_, 493 GetSystemPath(), 494 PP_FILEOPENFLAG_READ, 495 message_id)); 496 } else { 497 // Non-external file system 498 if (!HasValidFileSystem()) 499 return PP_ERROR_NOACCESS; 500 501 PP_FileSystemType file_system_type = GetFileSystemHost()->GetType(); 502 FileSystemDispatcher* file_system_dispatcher = 503 ChildThread::current()->file_system_dispatcher(); 504 file_system_dispatcher->ReadMetadata( 505 GetFileSystemURL(), 506 base::Bind(&DidReadMetadata, callback, info, file_system_type), 507 base::Bind(&DidFinishFileOperation, callback)); 508 } 509 return PP_OK_COMPLETIONPENDING; 510 } 511 512 int32_t PPB_FileRef_Impl::ReadDirectoryEntries( 513 const PP_ArrayOutput& output, 514 scoped_refptr<TrackedCallback> callback) { 515 NOTREACHED(); 516 return PP_ERROR_FAILED; 517 } 518 519 int32_t PPB_FileRef_Impl::ReadDirectoryEntriesInHost( 520 linked_ptr<std::vector<ppapi::PPB_FileRef_CreateInfo> > files, 521 linked_ptr<std::vector<PP_FileType> > file_types, 522 scoped_refptr<TrackedCallback> callback) { 523 if (!IsValidNonExternalFileSystem()) 524 return PP_ERROR_NOACCESS; 525 526 // TODO(yzshen): Passing base::Unretained(this) to the callback could 527 // be dangerous. 528 FileSystemDispatcher* file_system_dispatcher = 529 ChildThread::current()->file_system_dispatcher(); 530 file_system_dispatcher->ReadDirectory( 531 GetFileSystemURL(), 532 base::Bind(&DidReadDirectory, 533 callback, base::Unretained(this), files, file_types), 534 base::Bind(&DidFinishFileOperation, callback)); 535 return PP_OK_COMPLETIONPENDING; 536 } 537 538 PepperFileSystemHost* PPB_FileRef_Impl::GetFileSystemHost() const { 539 return GetFileSystemHostInternal(pp_instance(), file_system_); 540 } 541 542 PepperFileSystemHost* PPB_FileRef_Impl::GetFileSystemHostInternal( 543 PP_Instance instance, PP_Resource resource) { 544 const ppapi::host::PpapiHost* ppapi_host = 545 RendererPpapiHost::GetForPPInstance(instance)->GetPpapiHost(); 546 if (!resource || !ppapi_host) 547 return NULL; 548 ppapi::host::ResourceHost* resource_host = 549 ppapi_host->GetResourceHost(resource); 550 if (resource_host && resource_host->IsFileSystemHost()) 551 return static_cast<PepperFileSystemHost*>(resource_host); 552 return NULL; 553 } 554 555 } // namespace content 556