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_system_browser_host.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "content/browser/renderer_host/pepper/pepper_file_io_host.h"
10#include "content/browser/renderer_host/pepper/quota_reservation.h"
11#include "content/common/pepper_file_util.h"
12#include "content/public/browser/browser_ppapi_host.h"
13#include "content/public/browser/browser_thread.h"
14#include "content/public/browser/plugin_service.h"
15#include "content/public/browser/render_process_host.h"
16#include "content/public/browser/storage_partition.h"
17#include "content/public/common/pepper_plugin_info.h"
18#include "net/base/mime_util.h"
19#include "ppapi/c/pp_errors.h"
20#include "ppapi/host/dispatch_host_message.h"
21#include "ppapi/host/ppapi_host.h"
22#include "ppapi/proxy/ppapi_messages.h"
23#include "ppapi/shared_impl/file_system_util.h"
24#include "ppapi/shared_impl/file_type_conversion.h"
25#include "storage/browser/fileapi/file_system_operation_runner.h"
26#include "storage/browser/fileapi/isolated_context.h"
27#include "storage/browser/quota/quota_manager_proxy.h"
28#include "storage/common/fileapi/file_system_util.h"
29#include "storage/common/quota/quota_types.h"
30
31namespace content {
32
33namespace {
34
35// This is the minimum amount of quota we reserve per file system.
36const int64_t kMinimumQuotaReservationSize = 1024 * 1024;  // 1 MB
37
38scoped_refptr<storage::FileSystemContext> GetFileSystemContextFromRenderId(
39    int render_process_id) {
40  DCHECK_CURRENTLY_ON(BrowserThread::UI);
41  RenderProcessHost* host = RenderProcessHost::FromID(render_process_id);
42  if (!host)
43    return NULL;
44  StoragePartition* storage_partition = host->GetStoragePartition();
45  if (!storage_partition)
46    return NULL;
47  return storage_partition->GetFileSystemContext();
48}
49
50}  // namespace
51
52PepperFileSystemBrowserHost::PepperFileSystemBrowserHost(BrowserPpapiHost* host,
53                                                         PP_Instance instance,
54                                                         PP_Resource resource,
55                                                         PP_FileSystemType type)
56    : ResourceHost(host->GetPpapiHost(), instance, resource),
57      browser_ppapi_host_(host),
58      type_(type),
59      called_open_(false),
60      opened_(false),
61      file_system_context_(NULL),
62      reserved_quota_(0),
63      reserving_quota_(false),
64      weak_factory_(this) {}
65
66PepperFileSystemBrowserHost::~PepperFileSystemBrowserHost() {
67  // If |files_| is not empty, the plugin failed to close some files. It must
68  // have crashed.
69  if (!files_.empty()) {
70    file_system_context_->default_file_task_runner()->PostTask(
71        FROM_HERE,
72        base::Bind(&QuotaReservation::OnClientCrash, quota_reservation_));
73  }
74
75  // All FileRefs and FileIOs that reference us must have been destroyed. Cancel
76  // all pending file system operations.
77  if (file_system_operation_runner_)
78    file_system_operation_runner_->Shutdown();
79}
80
81void PepperFileSystemBrowserHost::OpenExisting(const GURL& root_url,
82                                               const base::Closure& callback) {
83  root_url_ = root_url;
84  int render_process_id = 0;
85  int unused;
86  if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
87          pp_instance(), &render_process_id, &unused)) {
88    NOTREACHED();
89  }
90  called_open_ = true;
91  // Get the file system context asynchronously, and then complete the Open
92  // operation by calling |callback|.
93  BrowserThread::PostTaskAndReplyWithResult(
94      BrowserThread::UI,
95      FROM_HERE,
96      base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
97      base::Bind(&PepperFileSystemBrowserHost::OpenExistingFileSystem,
98                 weak_factory_.GetWeakPtr(),
99                 callback));
100}
101
102int32_t PepperFileSystemBrowserHost::OnResourceMessageReceived(
103    const IPC::Message& msg,
104    ppapi::host::HostMessageContext* context) {
105  PPAPI_BEGIN_MESSAGE_MAP(PepperFileSystemBrowserHost, msg)
106    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileSystem_Open,
107                                      OnHostMsgOpen)
108    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
109        PpapiHostMsg_FileSystem_InitIsolatedFileSystem,
110        OnHostMsgInitIsolatedFileSystem)
111    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileSystem_ReserveQuota,
112                                      OnHostMsgReserveQuota)
113  PPAPI_END_MESSAGE_MAP()
114  return PP_ERROR_FAILED;
115}
116
117bool PepperFileSystemBrowserHost::IsFileSystemHost() { return true; }
118
119void PepperFileSystemBrowserHost::OpenQuotaFile(
120    PepperFileIOHost* file_io_host,
121    const storage::FileSystemURL& url,
122    const OpenQuotaFileCallback& callback) {
123  int32_t id = file_io_host->pp_resource();
124  std::pair<FileMap::iterator, bool> insert_result =
125      files_.insert(std::make_pair(id, file_io_host));
126  if (insert_result.second) {
127    base::PostTaskAndReplyWithResult(
128        file_system_context_->default_file_task_runner(),
129        FROM_HERE,
130        base::Bind(&QuotaReservation::OpenFile, quota_reservation_, id, url),
131        callback);
132  } else {
133    NOTREACHED();
134  }
135}
136
137void PepperFileSystemBrowserHost::CloseQuotaFile(
138    PepperFileIOHost* file_io_host,
139    const ppapi::FileGrowth& file_growth) {
140  int32_t id = file_io_host->pp_resource();
141  FileMap::iterator it = files_.find(id);
142  if (it != files_.end()) {
143    files_.erase(it);
144  } else {
145    NOTREACHED();
146    return;
147  }
148
149  file_system_context_->default_file_task_runner()->PostTask(
150      FROM_HERE,
151      base::Bind(
152          &QuotaReservation::CloseFile, quota_reservation_, id, file_growth));
153}
154
155int32_t PepperFileSystemBrowserHost::OnHostMsgOpen(
156    ppapi::host::HostMessageContext* context,
157    int64_t /* unused */) {
158  // TODO(raymes): The file system size is now unused by FileSystemDispatcher.
159  // Figure out why. Why is the file system size signed?
160
161  // Not allow multiple opens.
162  if (called_open_)
163    return PP_ERROR_INPROGRESS;
164  called_open_ = true;
165
166  storage::FileSystemType file_system_type =
167      PepperFileSystemTypeToFileSystemType(type_);
168  if (file_system_type == storage::kFileSystemTypeUnknown)
169    return PP_ERROR_FAILED;
170
171  int render_process_id = 0;
172  int unused;
173  if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
174          pp_instance(), &render_process_id, &unused)) {
175    return PP_ERROR_FAILED;
176  }
177
178  BrowserThread::PostTaskAndReplyWithResult(
179      BrowserThread::UI,
180      FROM_HERE,
181      base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
182      base::Bind(&PepperFileSystemBrowserHost::OpenFileSystem,
183                 weak_factory_.GetWeakPtr(),
184                 context->MakeReplyMessageContext(),
185                 file_system_type));
186  return PP_OK_COMPLETIONPENDING;
187}
188
189void PepperFileSystemBrowserHost::OpenExistingFileSystem(
190    const base::Closure& callback,
191    scoped_refptr<storage::FileSystemContext> file_system_context) {
192  if (file_system_context.get()) {
193    opened_ = true;
194  } else {
195    // If there is no file system context, we log a warning and continue with an
196    // invalid resource (which will produce errors when used), since we have no
197    // way to communicate the error to the caller.
198    LOG(WARNING) << "Could not retrieve file system context.";
199  }
200  SetFileSystemContext(file_system_context);
201
202  if (ShouldCreateQuotaReservation())
203    CreateQuotaReservation(callback);
204  else
205    callback.Run();
206}
207
208void PepperFileSystemBrowserHost::OpenFileSystem(
209    ppapi::host::ReplyMessageContext reply_context,
210    storage::FileSystemType file_system_type,
211    scoped_refptr<storage::FileSystemContext> file_system_context) {
212  if (!file_system_context.get()) {
213    OpenFileSystemComplete(
214        reply_context, GURL(), std::string(), base::File::FILE_ERROR_FAILED);
215    return;
216  }
217
218  SetFileSystemContext(file_system_context);
219
220  GURL origin =
221      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin();
222  file_system_context_->OpenFileSystem(
223      origin,
224      file_system_type,
225      storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
226      base::Bind(&PepperFileSystemBrowserHost::OpenFileSystemComplete,
227                 weak_factory_.GetWeakPtr(),
228                 reply_context));
229}
230
231void PepperFileSystemBrowserHost::OpenFileSystemComplete(
232    ppapi::host::ReplyMessageContext reply_context,
233    const GURL& root,
234    const std::string& /* unused */,
235    base::File::Error error) {
236  int32 pp_error = ppapi::FileErrorToPepperError(error);
237  if (pp_error == PP_OK) {
238    opened_ = true;
239    root_url_ = root;
240
241    if (ShouldCreateQuotaReservation()) {
242      CreateQuotaReservation(
243          base::Bind(&PepperFileSystemBrowserHost::SendReplyForFileSystem,
244                     weak_factory_.GetWeakPtr(),
245                     reply_context,
246                     static_cast<int32_t>(PP_OK)));
247      return;
248    }
249  }
250  SendReplyForFileSystem(reply_context, pp_error);
251}
252
253void PepperFileSystemBrowserHost::OpenIsolatedFileSystem(
254    ppapi::host::ReplyMessageContext reply_context,
255    const std::string& fsid,
256    PP_IsolatedFileSystemType_Private type,
257    scoped_refptr<storage::FileSystemContext> file_system_context) {
258  if (!file_system_context.get()) {
259    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
260    return;
261  }
262  SetFileSystemContext(file_system_context);
263
264  root_url_ = GURL(storage::GetIsolatedFileSystemRootURIString(
265      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin(),
266      fsid,
267      ppapi::IsolatedFileSystemTypeToRootName(type)));
268  if (!root_url_.is_valid()) {
269    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
270    return;
271  }
272
273  switch (type) {
274    case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_CRX:
275      opened_ = true;
276      SendReplyForIsolatedFileSystem(reply_context, fsid, PP_OK);
277      return;
278    case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE:
279      OpenPluginPrivateFileSystem(reply_context, fsid, file_system_context_);
280      return;
281    default:
282      NOTREACHED();
283      SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_BADARGUMENT);
284      return;
285  }
286}
287
288void PepperFileSystemBrowserHost::OpenPluginPrivateFileSystem(
289    ppapi::host::ReplyMessageContext reply_context,
290    const std::string& fsid,
291    scoped_refptr<storage::FileSystemContext> file_system_context) {
292  GURL origin =
293      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin();
294  if (!origin.is_valid()) {
295    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
296    return;
297  }
298
299  const std::string& plugin_id = GeneratePluginId(GetPluginMimeType());
300  if (plugin_id.empty()) {
301    SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_BADARGUMENT);
302    return;
303  }
304
305  file_system_context->OpenPluginPrivateFileSystem(
306      origin,
307      storage::kFileSystemTypePluginPrivate,
308      fsid,
309      plugin_id,
310      storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
311      base::Bind(
312          &PepperFileSystemBrowserHost::OpenPluginPrivateFileSystemComplete,
313          weak_factory_.GetWeakPtr(),
314          reply_context,
315          fsid));
316}
317
318void PepperFileSystemBrowserHost::OpenPluginPrivateFileSystemComplete(
319    ppapi::host::ReplyMessageContext reply_context,
320    const std::string& fsid,
321    base::File::Error error) {
322  int32 pp_error = ppapi::FileErrorToPepperError(error);
323  if (pp_error == PP_OK)
324    opened_ = true;
325  SendReplyForIsolatedFileSystem(reply_context, fsid, pp_error);
326}
327
328int32_t PepperFileSystemBrowserHost::OnHostMsgInitIsolatedFileSystem(
329    ppapi::host::HostMessageContext* context,
330    const std::string& fsid,
331    PP_IsolatedFileSystemType_Private type) {
332  // Do not allow multiple opens.
333  if (called_open_)
334    return PP_ERROR_INPROGRESS;
335  called_open_ = true;
336
337  // Do a sanity check.
338  if (!storage::ValidateIsolatedFileSystemId(fsid))
339    return PP_ERROR_BADARGUMENT;
340
341  int render_process_id = 0;
342  int unused;
343  if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
344          pp_instance(), &render_process_id, &unused)) {
345    storage::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
346    return PP_ERROR_FAILED;
347  }
348
349  root_url_ = GURL(storage::GetIsolatedFileSystemRootURIString(
350      browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()).GetOrigin(),
351      fsid,
352      ppapi::IsolatedFileSystemTypeToRootName(type)));
353
354  BrowserThread::PostTaskAndReplyWithResult(
355      BrowserThread::UI,
356      FROM_HERE,
357      base::Bind(&GetFileSystemContextFromRenderId, render_process_id),
358      base::Bind(&PepperFileSystemBrowserHost::OpenIsolatedFileSystem,
359                 weak_factory_.GetWeakPtr(),
360                 context->MakeReplyMessageContext(),
361                 fsid,
362                 type));
363  return PP_OK_COMPLETIONPENDING;
364}
365
366int32_t PepperFileSystemBrowserHost::OnHostMsgReserveQuota(
367    ppapi::host::HostMessageContext* context,
368    int64_t amount,
369    const ppapi::FileGrowthMap& file_growths) {
370  DCHECK(ChecksQuota());
371  DCHECK_GT(amount, 0);
372
373  if (reserving_quota_)
374    return PP_ERROR_INPROGRESS;
375  reserving_quota_ = true;
376
377  int64_t reservation_amount =
378      std::max<int64_t>(kMinimumQuotaReservationSize, amount);
379  file_system_context_->default_file_task_runner()->PostTask(
380      FROM_HERE,
381      base::Bind(&QuotaReservation::ReserveQuota,
382                 quota_reservation_,
383                 reservation_amount,
384                 file_growths,
385                 base::Bind(&PepperFileSystemBrowserHost::GotReservedQuota,
386                            weak_factory_.GetWeakPtr(),
387                            context->MakeReplyMessageContext())));
388
389  return PP_OK_COMPLETIONPENDING;
390}
391
392void PepperFileSystemBrowserHost::SendReplyForFileSystem(
393    ppapi::host::ReplyMessageContext reply_context,
394    int32_t pp_error) {
395  reply_context.params.set_result(pp_error);
396  host()->SendReply(reply_context, PpapiPluginMsg_FileSystem_OpenReply());
397}
398
399void PepperFileSystemBrowserHost::SendReplyForIsolatedFileSystem(
400    ppapi::host::ReplyMessageContext reply_context,
401    const std::string& fsid,
402    int32_t error) {
403  if (error != PP_OK)
404    storage::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
405  reply_context.params.set_result(error);
406  host()->SendReply(reply_context,
407                    PpapiPluginMsg_FileSystem_InitIsolatedFileSystemReply());
408}
409
410void PepperFileSystemBrowserHost::SetFileSystemContext(
411    scoped_refptr<storage::FileSystemContext> file_system_context) {
412  file_system_context_ = file_system_context;
413  if (type_ != PP_FILESYSTEMTYPE_EXTERNAL || root_url_.is_valid()) {
414    file_system_operation_runner_ =
415        file_system_context_->CreateFileSystemOperationRunner();
416  }
417}
418
419bool PepperFileSystemBrowserHost::ShouldCreateQuotaReservation() const {
420  // Some file system types don't have quota.
421  if (!ppapi::FileSystemTypeHasQuota(type_))
422    return false;
423
424  // For file system types with quota, some origins have unlimited storage.
425  storage::QuotaManagerProxy* quota_manager_proxy =
426      file_system_context_->quota_manager_proxy();
427  CHECK(quota_manager_proxy);
428  CHECK(quota_manager_proxy->quota_manager());
429  storage::FileSystemType file_system_type =
430      PepperFileSystemTypeToFileSystemType(type_);
431  return !quota_manager_proxy->quota_manager()->IsStorageUnlimited(
432      root_url_.GetOrigin(),
433      storage::FileSystemTypeToQuotaStorageType(file_system_type));
434}
435
436void PepperFileSystemBrowserHost::CreateQuotaReservation(
437    const base::Closure& callback) {
438  DCHECK(root_url_.is_valid());
439  base::PostTaskAndReplyWithResult(
440      file_system_context_->default_file_task_runner(),
441      FROM_HERE,
442      base::Bind(&QuotaReservation::Create,
443                 file_system_context_,
444                 root_url_.GetOrigin(),
445                 PepperFileSystemTypeToFileSystemType(type_)),
446      base::Bind(&PepperFileSystemBrowserHost::GotQuotaReservation,
447                 weak_factory_.GetWeakPtr(),
448                 callback));
449}
450
451void PepperFileSystemBrowserHost::GotQuotaReservation(
452    const base::Closure& callback,
453    scoped_refptr<QuotaReservation> quota_reservation) {
454  quota_reservation_ = quota_reservation;
455  callback.Run();
456}
457
458void PepperFileSystemBrowserHost::GotReservedQuota(
459    ppapi::host::ReplyMessageContext reply_context,
460    int64_t amount,
461    const ppapi::FileSizeMap& file_sizes) {
462  DCHECK(reserving_quota_);
463  reserving_quota_ = false;
464  reserved_quota_ = amount;
465
466  reply_context.params.set_result(PP_OK);
467  host()->SendReply(
468      reply_context,
469      PpapiPluginMsg_FileSystem_ReserveQuotaReply(amount, file_sizes));
470}
471
472std::string PepperFileSystemBrowserHost::GetPluginMimeType() const {
473  base::FilePath plugin_path = browser_ppapi_host_->GetPluginPath();
474  PepperPluginInfo* info =
475      PluginService::GetInstance()->GetRegisteredPpapiPluginInfo(plugin_path);
476  if (!info || info->mime_types.empty())
477    return std::string();
478  // Use the first element in |info->mime_types| even if several elements exist.
479  return info->mime_types[0].mime_type;
480}
481
482std::string PepperFileSystemBrowserHost::GeneratePluginId(
483    const std::string& mime_type) const {
484  // TODO(nhiroki): This function is very specialized for specific plugins (MIME
485  // types).  If we bring this API to stable, we might have to make it more
486  // general.
487
488  std::string top_level_type;
489  std::string subtype;
490  if (!net::ParseMimeTypeWithoutParameter(
491          mime_type, &top_level_type, &subtype) ||
492      !net::IsValidTopLevelMimeType(top_level_type))
493    return std::string();
494
495  // Replace a slash used for type/subtype separator with an underscore.
496  std::string output = top_level_type + "_" + subtype;
497
498  // Verify |output| contains only alphabets, digits, or "._-".
499  for (std::string::const_iterator it = output.begin(); it != output.end();
500       ++it) {
501    if (!IsAsciiAlpha(*it) && !IsAsciiDigit(*it) && *it != '.' && *it != '_' &&
502        *it != '-') {
503      LOG(WARNING) << "Failed to generate a plugin id.";
504      return std::string();
505    }
506  }
507  return output;
508}
509
510}  // namespace content
511