1// Copyright 2013 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/browser/renderer_host/pepper/pepper_file_io_host.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/callback_helpers.h"
10#include "base/files/file_util_proxy.h"
11#include "base/memory/weak_ptr.h"
12#include "content/browser/renderer_host/pepper/pepper_file_ref_host.h"
13#include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
14#include "content/browser/renderer_host/pepper/pepper_security_helper.h"
15#include "content/common/fileapi/file_system_messages.h"
16#include "content/common/sandbox_util.h"
17#include "content/common/view_messages.h"
18#include "content/public/browser/browser_thread.h"
19#include "content/public/browser/content_browser_client.h"
20#include "content/public/browser/render_process_host.h"
21#include "content/public/browser/storage_partition.h"
22#include "content/public/common/content_client.h"
23#include "ppapi/c/pp_errors.h"
24#include "ppapi/c/ppb_file_io.h"
25#include "ppapi/host/dispatch_host_message.h"
26#include "ppapi/host/ppapi_host.h"
27#include "ppapi/proxy/ppapi_messages.h"
28#include "ppapi/shared_impl/file_system_util.h"
29#include "ppapi/shared_impl/file_type_conversion.h"
30#include "ppapi/shared_impl/time_conversion.h"
31#include "webkit/browser/fileapi/file_observers.h"
32#include "webkit/browser/fileapi/file_system_context.h"
33#include "webkit/browser/fileapi/file_system_operation_runner.h"
34#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
35#include "webkit/common/fileapi/file_system_util.h"
36
37namespace content {
38
39using ppapi::FileIOStateManager;
40using ppapi::PPTimeToTime;
41
42namespace {
43
44PepperFileIOHost::UIThreadStuff GetUIThreadStuffForInternalFileSystems(
45    int render_process_id) {
46  PepperFileIOHost::UIThreadStuff stuff;
47  DCHECK_CURRENTLY_ON(BrowserThread::UI);
48  RenderProcessHost* host = RenderProcessHost::FromID(render_process_id);
49  if (host) {
50    stuff.resolved_render_process_id = base::GetProcId(host->GetHandle());
51    StoragePartition* storage_partition = host->GetStoragePartition();
52    if (storage_partition)
53      stuff.file_system_context = storage_partition->GetFileSystemContext();
54  }
55  return stuff;
56}
57
58base::ProcessId GetResolvedRenderProcessId(int render_process_id) {
59  DCHECK_CURRENTLY_ON(BrowserThread::UI);
60  RenderProcessHost* host = RenderProcessHost::FromID(render_process_id);
61  if (!host)
62    return base::kNullProcessId;
63  return base::GetProcId(host->GetHandle());
64}
65
66bool GetPluginAllowedToCallRequestOSFileHandle(int render_process_id,
67                                               const GURL& document_url) {
68  DCHECK_CURRENTLY_ON(BrowserThread::UI);
69  ContentBrowserClient* client = GetContentClient()->browser();
70  RenderProcessHost* host = RenderProcessHost::FromID(render_process_id);
71  if (!host)
72    return false;
73  return client->IsPluginAllowedToCallRequestOSFileHandle(
74      host->GetBrowserContext(), document_url);
75}
76
77bool FileOpenForWrite(int32_t open_flags) {
78  return (open_flags & (PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_APPEND)) != 0;
79}
80
81void FileCloser(base::File auto_close) {
82}
83
84void DidCloseFile(const base::Closure& on_close_callback) {
85  if (!on_close_callback.is_null())
86    on_close_callback.Run();
87}
88
89void DidOpenFile(base::WeakPtr<PepperFileIOHost> file_host,
90                 fileapi::FileSystemOperation::OpenFileCallback callback,
91                 base::File file,
92                 const base::Closure& on_close_callback) {
93  if (file_host) {
94    callback.Run(file.Pass(), on_close_callback);
95  } else {
96    BrowserThread::PostTaskAndReply(
97        BrowserThread::FILE,
98        FROM_HERE,
99        base::Bind(&FileCloser, base::Passed(&file)),
100        base::Bind(&DidCloseFile, on_close_callback));
101  }
102}
103
104}  // namespace
105
106PepperFileIOHost::PepperFileIOHost(BrowserPpapiHostImpl* host,
107                                   PP_Instance instance,
108                                   PP_Resource resource)
109    : ResourceHost(host->GetPpapiHost(), instance, resource),
110      browser_ppapi_host_(host),
111      render_process_host_(NULL),
112      file_(BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)),
113      open_flags_(0),
114      file_system_type_(PP_FILESYSTEMTYPE_INVALID),
115      max_written_offset_(0),
116      check_quota_(false) {
117  int unused;
118  if (!host->GetRenderFrameIDsForInstance(
119          instance, &render_process_id_, &unused)) {
120    render_process_id_ = -1;
121  }
122}
123
124PepperFileIOHost::~PepperFileIOHost() {}
125
126int32_t PepperFileIOHost::OnResourceMessageReceived(
127    const IPC::Message& msg,
128    ppapi::host::HostMessageContext* context) {
129  PPAPI_BEGIN_MESSAGE_MAP(PepperFileIOHost, msg)
130    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Open, OnHostMsgOpen)
131    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Touch, OnHostMsgTouch)
132    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_SetLength,
133                                      OnHostMsgSetLength)
134    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileIO_Flush,
135                                        OnHostMsgFlush)
136    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Close, OnHostMsgClose)
137    PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileIO_RequestOSFileHandle,
138                                        OnHostMsgRequestOSFileHandle)
139  PPAPI_END_MESSAGE_MAP()
140  return PP_ERROR_FAILED;
141}
142
143PepperFileIOHost::UIThreadStuff::UIThreadStuff() {
144  resolved_render_process_id = base::kNullProcessId;
145}
146
147PepperFileIOHost::UIThreadStuff::~UIThreadStuff() {}
148
149int32_t PepperFileIOHost::OnHostMsgOpen(
150    ppapi::host::HostMessageContext* context,
151    PP_Resource file_ref_resource,
152    int32_t open_flags) {
153  int32_t rv = state_manager_.CheckOperationState(
154      FileIOStateManager::OPERATION_EXCLUSIVE, false);
155  if (rv != PP_OK)
156    return rv;
157
158  int platform_file_flags = 0;
159  if (!ppapi::PepperFileOpenFlagsToPlatformFileFlags(open_flags,
160                                                     &platform_file_flags))
161    return PP_ERROR_BADARGUMENT;
162
163  ppapi::host::ResourceHost* resource_host =
164      host()->GetResourceHost(file_ref_resource);
165  if (!resource_host || !resource_host->IsFileRefHost())
166    return PP_ERROR_BADRESOURCE;
167  PepperFileRefHost* file_ref_host =
168      static_cast<PepperFileRefHost*>(resource_host);
169  if (file_ref_host->GetFileSystemType() == PP_FILESYSTEMTYPE_INVALID)
170    return PP_ERROR_FAILED;
171
172  file_system_host_ = file_ref_host->GetFileSystemHost();
173
174  open_flags_ = open_flags;
175  file_system_type_ = file_ref_host->GetFileSystemType();
176  file_system_url_ = file_ref_host->GetFileSystemURL();
177
178  // For external file systems, if there is a valid FileSystemURL, then treat
179  // it like internal file systems and access it via the FileSystemURL.
180  bool is_internal_type = (file_system_type_ != PP_FILESYSTEMTYPE_EXTERNAL) ||
181                          file_system_url_.is_valid();
182
183  if (is_internal_type) {
184    if (!file_system_url_.is_valid())
185      return PP_ERROR_BADARGUMENT;
186
187    // Not all external file systems are fully supported yet.
188    // Whitelist the supported ones.
189    if (file_system_url_.mount_type() == fileapi::kFileSystemTypeExternal) {
190      switch (file_system_url_.type()) {
191        case fileapi::kFileSystemTypeNativeMedia:
192        case fileapi::kFileSystemTypeDeviceMedia:
193        case fileapi::kFileSystemTypePicasa:
194        case fileapi::kFileSystemTypeItunes:
195        case fileapi::kFileSystemTypeIphoto:
196          break;
197        default:
198          return PP_ERROR_NOACCESS;
199      }
200    }
201    if (!CanOpenFileSystemURLWithPepperFlags(
202            open_flags, render_process_id_, file_system_url_))
203      return PP_ERROR_NOACCESS;
204    BrowserThread::PostTaskAndReplyWithResult(
205        BrowserThread::UI,
206        FROM_HERE,
207        base::Bind(&GetUIThreadStuffForInternalFileSystems, render_process_id_),
208        base::Bind(&PepperFileIOHost::GotUIThreadStuffForInternalFileSystems,
209                   AsWeakPtr(),
210                   context->MakeReplyMessageContext(),
211                   platform_file_flags));
212  } else {
213    base::FilePath path = file_ref_host->GetExternalFilePath();
214    if (!CanOpenWithPepperFlags(open_flags, render_process_id_, path))
215      return PP_ERROR_NOACCESS;
216    BrowserThread::PostTaskAndReplyWithResult(
217        BrowserThread::UI,
218        FROM_HERE,
219        base::Bind(&GetResolvedRenderProcessId, render_process_id_),
220        base::Bind(&PepperFileIOHost::GotResolvedRenderProcessId,
221                   AsWeakPtr(),
222                   context->MakeReplyMessageContext(),
223                   path,
224                   platform_file_flags));
225  }
226  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
227  return PP_OK_COMPLETIONPENDING;
228}
229
230void PepperFileIOHost::GotUIThreadStuffForInternalFileSystems(
231    ppapi::host::ReplyMessageContext reply_context,
232    int platform_file_flags,
233    UIThreadStuff ui_thread_stuff) {
234  DCHECK_CURRENTLY_ON(BrowserThread::IO);
235  file_system_context_ = ui_thread_stuff.file_system_context;
236  resolved_render_process_id_ = ui_thread_stuff.resolved_render_process_id;
237  if (resolved_render_process_id_ == base::kNullProcessId ||
238      !file_system_context_.get()) {
239    reply_context.params.set_result(PP_ERROR_FAILED);
240    SendOpenErrorReply(reply_context);
241    return;
242  }
243
244  if (!file_system_context_->GetFileSystemBackend(file_system_url_.type())) {
245    reply_context.params.set_result(PP_ERROR_FAILED);
246    SendOpenErrorReply(reply_context);
247    return;
248  }
249
250  DCHECK(file_system_host_.get());
251  DCHECK(file_system_host_->GetFileSystemOperationRunner());
252
253  file_system_host_->GetFileSystemOperationRunner()->OpenFile(
254      file_system_url_,
255      platform_file_flags,
256      base::Bind(&DidOpenFile,
257                 AsWeakPtr(),
258                 base::Bind(&PepperFileIOHost::DidOpenInternalFile,
259                            AsWeakPtr(),
260                            reply_context)));
261}
262
263void PepperFileIOHost::DidOpenInternalFile(
264    ppapi::host::ReplyMessageContext reply_context,
265    base::File file,
266    const base::Closure& on_close_callback) {
267  if (file.IsValid()) {
268    on_close_callback_ = on_close_callback;
269
270    if (FileOpenForWrite(open_flags_) && file_system_host_->ChecksQuota()) {
271      check_quota_ = true;
272      file_system_host_->OpenQuotaFile(
273          this,
274          file_system_url_,
275          base::Bind(&PepperFileIOHost::DidOpenQuotaFile,
276                     AsWeakPtr(),
277                     reply_context,
278                     base::Passed(&file)));
279      return;
280    }
281  }
282
283  DCHECK(!file_.IsValid());
284  base::File::Error error =
285      file.IsValid() ? base::File::FILE_OK : file.error_details();
286  file_.SetFile(file.Pass());
287  OnOpenProxyCallback(reply_context, error);
288}
289
290void PepperFileIOHost::GotResolvedRenderProcessId(
291    ppapi::host::ReplyMessageContext reply_context,
292    base::FilePath path,
293    int file_flags,
294    base::ProcessId resolved_render_process_id) {
295  DCHECK_CURRENTLY_ON(BrowserThread::IO);
296  resolved_render_process_id_ = resolved_render_process_id;
297  file_.CreateOrOpen(
298      path,
299      file_flags,
300      base::Bind(&PepperFileIOHost::OnOpenProxyCallback,
301                 AsWeakPtr(),
302                 reply_context));
303}
304
305int32_t PepperFileIOHost::OnHostMsgTouch(
306    ppapi::host::HostMessageContext* context,
307    PP_Time last_access_time,
308    PP_Time last_modified_time) {
309  int32_t rv = state_manager_.CheckOperationState(
310      FileIOStateManager::OPERATION_EXCLUSIVE, true);
311  if (rv != PP_OK)
312    return rv;
313
314  if (!file_.SetTimes(
315          PPTimeToTime(last_access_time),
316          PPTimeToTime(last_modified_time),
317          base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback,
318                     AsWeakPtr(),
319                     context->MakeReplyMessageContext()))) {
320    return PP_ERROR_FAILED;
321  }
322
323  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
324  return PP_OK_COMPLETIONPENDING;
325}
326
327int32_t PepperFileIOHost::OnHostMsgSetLength(
328    ppapi::host::HostMessageContext* context,
329    int64_t length) {
330  int32_t rv = state_manager_.CheckOperationState(
331      FileIOStateManager::OPERATION_EXCLUSIVE, true);
332  if (rv != PP_OK)
333    return rv;
334  if (length < 0)
335    return PP_ERROR_BADARGUMENT;
336
337  // Quota checks are performed on the plugin side, in order to use the same
338  // quota reservation and request system as Write.
339
340  if (!file_.SetLength(
341          length,
342          base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback,
343                     AsWeakPtr(),
344                     context->MakeReplyMessageContext()))) {
345    return PP_ERROR_FAILED;
346  }
347
348  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
349  return PP_OK_COMPLETIONPENDING;
350}
351
352int32_t PepperFileIOHost::OnHostMsgFlush(
353    ppapi::host::HostMessageContext* context) {
354  int32_t rv = state_manager_.CheckOperationState(
355      FileIOStateManager::OPERATION_EXCLUSIVE, true);
356  if (rv != PP_OK)
357    return rv;
358
359  if (!file_.Flush(
360          base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback,
361                     AsWeakPtr(),
362                     context->MakeReplyMessageContext()))) {
363    return PP_ERROR_FAILED;
364  }
365
366  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
367  return PP_OK_COMPLETIONPENDING;
368}
369
370int32_t PepperFileIOHost::OnHostMsgClose(
371    ppapi::host::HostMessageContext* context,
372    const ppapi::FileGrowth& file_growth) {
373  if (check_quota_) {
374    file_system_host_->CloseQuotaFile(this, file_growth);
375    check_quota_ = false;
376  }
377
378  if (file_.IsValid()) {
379    file_.Close(base::Bind(&PepperFileIOHost::DidCloseFile,
380                           AsWeakPtr()));
381  }
382  return PP_OK;
383}
384
385void PepperFileIOHost::DidOpenQuotaFile(
386    ppapi::host::ReplyMessageContext reply_context,
387    base::File file,
388    int64_t max_written_offset) {
389  DCHECK(!file_.IsValid());
390  DCHECK(file.IsValid());
391  max_written_offset_ = max_written_offset;
392  file_.SetFile(file.Pass());
393
394  OnOpenProxyCallback(reply_context, base::File::FILE_OK);
395}
396
397void PepperFileIOHost::DidCloseFile(base::File::Error /*error*/) {
398  // Silently ignore if we fail to close the file.
399  if (!on_close_callback_.is_null()) {
400    on_close_callback_.Run();
401    on_close_callback_.Reset();
402  }
403}
404
405int32_t PepperFileIOHost::OnHostMsgRequestOSFileHandle(
406    ppapi::host::HostMessageContext* context) {
407  if (open_flags_ != PP_FILEOPENFLAG_READ && file_system_host_->ChecksQuota())
408    return PP_ERROR_FAILED;
409
410  GURL document_url =
411      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance());
412  BrowserThread::PostTaskAndReplyWithResult(
413      BrowserThread::UI,
414      FROM_HERE,
415      base::Bind(&GetPluginAllowedToCallRequestOSFileHandle,
416                 render_process_id_,
417                 document_url),
418      base::Bind(&PepperFileIOHost::GotPluginAllowedToCallRequestOSFileHandle,
419                 AsWeakPtr(),
420                 context->MakeReplyMessageContext()));
421  return PP_OK_COMPLETIONPENDING;
422}
423
424void PepperFileIOHost::GotPluginAllowedToCallRequestOSFileHandle(
425    ppapi::host::ReplyMessageContext reply_context,
426    bool plugin_allowed) {
427  DCHECK_CURRENTLY_ON(BrowserThread::IO);
428  if (!browser_ppapi_host_->external_plugin() ||
429      host()->permissions().HasPermission(ppapi::PERMISSION_PRIVATE) ||
430      plugin_allowed) {
431    if (!AddFileToReplyContext(open_flags_, &reply_context))
432      reply_context.params.set_result(PP_ERROR_FAILED);
433  } else {
434    reply_context.params.set_result(PP_ERROR_NOACCESS);
435  }
436  host()->SendReply(reply_context,
437                    PpapiPluginMsg_FileIO_RequestOSFileHandleReply());
438}
439
440void PepperFileIOHost::ExecutePlatformGeneralCallback(
441    ppapi::host::ReplyMessageContext reply_context,
442    base::File::Error error_code) {
443  reply_context.params.set_result(ppapi::FileErrorToPepperError(error_code));
444  host()->SendReply(reply_context, PpapiPluginMsg_FileIO_GeneralReply());
445  state_manager_.SetOperationFinished();
446}
447
448void PepperFileIOHost::OnOpenProxyCallback(
449    ppapi::host::ReplyMessageContext reply_context,
450    base::File::Error error_code) {
451  int32_t pp_error = ppapi::FileErrorToPepperError(error_code);
452  if (file_.IsValid() && !AddFileToReplyContext(open_flags_, &reply_context))
453    pp_error = PP_ERROR_FAILED;
454
455  PP_Resource quota_file_system = 0;
456  if (pp_error == PP_OK) {
457    state_manager_.SetOpenSucceed();
458    // A non-zero resource id signals the plugin side to check quota.
459    if (check_quota_)
460      quota_file_system = file_system_host_->pp_resource();
461  }
462
463  reply_context.params.set_result(pp_error);
464  host()->SendReply(
465      reply_context,
466      PpapiPluginMsg_FileIO_OpenReply(quota_file_system, max_written_offset_));
467  state_manager_.SetOperationFinished();
468}
469
470void PepperFileIOHost::SendOpenErrorReply(
471    ppapi::host::ReplyMessageContext reply_context) {
472  host()->SendReply(reply_context, PpapiPluginMsg_FileIO_OpenReply(0, 0));
473}
474
475bool PepperFileIOHost::AddFileToReplyContext(
476    int32_t open_flags,
477    ppapi::host::ReplyMessageContext* reply_context) const {
478  base::ProcessId plugin_process_id =
479      base::GetProcId(browser_ppapi_host_->GetPluginProcessHandle());
480  if (plugin_process_id == base::kNullProcessId)
481    plugin_process_id = resolved_render_process_id_;
482
483  IPC::PlatformFileForTransit transit_file =
484      BrokerGetFileHandleForProcess(file_.GetPlatformFile(), plugin_process_id,
485                                    false);
486  if (transit_file == IPC::InvalidPlatformFileForTransit())
487    return false;
488
489  ppapi::proxy::SerializedHandle file_handle;
490  // A non-zero resource id signals NaClIPCAdapter to create a NaClQuotaDesc.
491  PP_Resource quota_file_io = check_quota_ ? pp_resource() : 0;
492  file_handle.set_file_handle(transit_file, open_flags, quota_file_io);
493  reply_context->params.AppendHandle(file_handle);
494  return true;
495}
496
497}  // namespace content
498