/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/dom/FileSystemRequestParent.h"
#include "mozilla/dom/PFileSystemParams.h"

#include "GetDirectoryListingTask.h"
#include "GetFileOrDirectoryTask.h"
#include "GetFilesTask.h"

#include "mozilla/dom/BlobImpl.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/FileSystemBase.h"
#include "mozilla/dom/FileSystemSecurity.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/dom/OSFileSystem.h"
#include "mozilla/Preferences.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Unused.h"
#include "nsProxyRelease.h"

using namespace mozilla::ipc;

namespace mozilla::dom {

FileSystemRequestParent::FileSystemRequestParent() : mDestroyed(false) {
  AssertIsOnBackgroundThread();
}

FileSystemRequestParent::~FileSystemRequestParent() {
  AssertIsOnBackgroundThread();
}

#define FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(name)          \
  case FileSystemParams::TFileSystem##name##Params: {           \
    const FileSystem##name##Params& p = aParams;                \
    mFileSystem = new OSFileSystemParent(p.filesystem());       \
    MOZ_ASSERT(mFileSystem);                                    \
    mTask = name##TaskParent::Create(mFileSystem, p, this, rv); \
    if (NS_WARN_IF(rv.Failed())) {                              \
      rv.SuppressException();                                   \
      return false;                                             \
    }                                                           \
    break;                                                      \
  }

bool FileSystemRequestParent::Initialize(const FileSystemParams& aParams) {
  AssertIsOnBackgroundThread();

  ErrorResult rv;

  switch (aParams.type()) {
    FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetDirectoryListing)
    FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFileOrDirectory)
    FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(GetFiles)

    default: {
      MOZ_CRASH("not reached");
      break;
    }
  }

  if (NS_WARN_IF(!mTask || !mFileSystem)) {
    // Should never reach here.
    return false;
  }

  return true;
}

namespace {

class CheckPermissionRunnable final : public Runnable {
 public:
  CheckPermissionRunnable(
      already_AddRefed<ThreadsafeContentParentHandle> aParent,
      FileSystemRequestParent* aActor, FileSystemTaskParentBase* aTask,
      const nsAString& aPath)
      : Runnable("dom::CheckPermissionRunnable"),
        mContentHandle(aParent),
        mActor(aActor),
        mTask(aTask),
        mPath(aPath),
        mBackgroundEventTarget(GetCurrentSerialEventTarget()) {
    AssertIsInMainProcess();
    AssertIsOnBackgroundThread();

    MOZ_ASSERT(mContentHandle);
    MOZ_ASSERT(mActor);
    MOZ_ASSERT(mTask);
    MOZ_ASSERT(mBackgroundEventTarget);
  }

  NS_IMETHOD
  Run() override {
    if (NS_IsMainThread()) {
      if (!mozilla::Preferences::GetBool("dom.filesystem.pathcheck.disabled",
                                         false)) {
        RefPtr<FileSystemSecurity> fss = FileSystemSecurity::Get();
        if (NS_WARN_IF(!fss || !fss->ContentProcessHasAccessTo(
                                   mContentHandle->ChildID(), mPath))) {
          AssertIsOnMainThread();
          if (RefPtr<ContentParent> contentParent =
                  mContentHandle->GetContentParent()) {
            contentParent->KillHard("This path is not allowed.");
          }
          return NS_OK;
        }
      }

      return mBackgroundEventTarget->Dispatch(this, NS_DISPATCH_NORMAL);
    }

    AssertIsOnBackgroundThread();

    // It can happen that this actor has been destroyed in the meantime we were
    // on the main-thread.
    if (!mActor->Destroyed()) {
      mTask->Start();
    }

    return NS_OK;
  }

 private:
  ~CheckPermissionRunnable() {
    NS_ProxyRelease("CheckPermissionRunnable::mActor", mBackgroundEventTarget,
                    mActor.forget());
  }

  RefPtr<ThreadsafeContentParentHandle> mContentHandle;
  RefPtr<FileSystemRequestParent> mActor;
  RefPtr<FileSystemTaskParentBase> mTask;
  const nsString mPath;

  nsCOMPtr<nsIEventTarget> mBackgroundEventTarget;
};

}  // namespace

void FileSystemRequestParent::Start() {
  AssertIsInMainProcess();
  AssertIsOnBackgroundThread();

  MOZ_ASSERT(!mDestroyed);
  MOZ_ASSERT(mFileSystem);
  MOZ_ASSERT(mTask);

  nsAutoString path;
  if (NS_WARN_IF(NS_FAILED(mTask->GetTargetPath(path)))) {
    (void)Send__delete__(this,
                         FileSystemErrorResponse(NS_ERROR_DOM_SECURITY_ERR));
    return;
  }

  RefPtr<ThreadsafeContentParentHandle> parent =
      BackgroundParent::GetContentParentHandle(Manager());

  // If the ThreadsafeContentParentHandle is null we are dealing with a
  // same-process actor.
  if (!parent) {
    mTask->Start();
    return;
  }

  RefPtr<Runnable> runnable =
      new CheckPermissionRunnable(parent.forget(), this, mTask, path);
  NS_DispatchToMainThread(runnable);
}

void FileSystemRequestParent::ActorDestroy(ActorDestroyReason aWhy) {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(!mDestroyed);

  if (!mFileSystem) {
    return;
  }

  mFileSystem->Shutdown();
  mFileSystem = nullptr;
  mTask = nullptr;
  mDestroyed = true;
}

}  // namespace mozilla::dom
