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