/*
 * Copyright (C) 2013 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/core/fileapi/file_error.h"

#include "third_party/blink/renderer/bindings/core/v8/v8_quota_exceeded_error_options.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/dom/quota_exceeded_error.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"

namespace blink {

namespace file_error {

const char kAbortErrorMessage[] =
    "An ongoing operation was aborted, typically with a call to abort().";
const char kEncodingErrorMessage[] =
    "A URI supplied to the API was malformed, or the resulting Data URL has "
    "exceeded the URL length limitations for Data URLs.";
const char kInvalidStateErrorMessage[] =
    "An operation that depends on state cached in an interface object was made "
    "but the state had changed since it was read from disk.";
const char kNoModificationAllowedErrorMessage[] =
    "An attempt was made to write to a file or directory which could not be "
    "modified due to the state of the underlying filesystem.";
const char kNotFoundErrorMessage[] =
    "A requested file or directory could not be found at the time an operation "
    "was processed.";
const char kNotReadableErrorMessage[] =
    "The requested file could not be read, typically due to permission "
    "problems that have occurred after a reference to a file was acquired.";
const char kPathExistsErrorMessage[] =
    "An attempt was made to create a file or directory where an element "
    "already exists.";
const char kQuotaExceededErrorMessage[] =
    "The operation failed because it would cause the application to exceed its "
    "storage quota.";
const char kSecurityErrorMessage[] =
    "It was determined that certain files are unsafe for access within a Web "
    "application, or that too many calls are being made on file resources.";
const char kSyntaxErrorMessage[] =
    "An invalid or unsupported argument was given, like an invalid line ending "
    "specifier.";
const char kTypeMismatchErrorMessage[] =
    "The path supplied exists, but was not an entry of requested type.";

namespace {

DOMExceptionCode ErrorCodeToExceptionCode(FileErrorCode code) {
  switch (code) {
    case FileErrorCode::kOK:
      return DOMExceptionCode::kNoError;
    case FileErrorCode::kNotFoundErr:
      return DOMExceptionCode::kNotFoundError;
    case FileErrorCode::kSecurityErr:
      return DOMExceptionCode::kSecurityError;
    case FileErrorCode::kAbortErr:
      return DOMExceptionCode::kAbortError;
    case FileErrorCode::kNotReadableErr:
      return DOMExceptionCode::kNotReadableError;
    case FileErrorCode::kEncodingErr:
      return DOMExceptionCode::kEncodingError;
    case FileErrorCode::kNoModificationAllowedErr:
      return DOMExceptionCode::kNoModificationAllowedError;
    case FileErrorCode::kInvalidStateErr:
      return DOMExceptionCode::kInvalidStateError;
    case FileErrorCode::kSyntaxErr:
      return DOMExceptionCode::kSyntaxError;
    case FileErrorCode::kInvalidModificationErr:
      return DOMExceptionCode::kInvalidModificationError;
    case FileErrorCode::kQuotaExceededErr:
      return DOMExceptionCode::kQuotaExceededError;
    case FileErrorCode::kTypeMismatchErr:
      return DOMExceptionCode::kTypeMismatchError;
    case FileErrorCode::kPathExistsErr:
      return DOMExceptionCode::kPathExistsError;
    default:
      NOTREACHED();
  }
}

const char* ErrorCodeToMessage(FileErrorCode code) {
  // Note that some of these do not set message. If message is 0 then the
  // default message is used.
  switch (code) {
    case FileErrorCode::kOK:
      return nullptr;
    case FileErrorCode::kSecurityErr:
      return kSecurityErrorMessage;
    case FileErrorCode::kNotFoundErr:
      return kNotFoundErrorMessage;
    case FileErrorCode::kAbortErr:
      return kAbortErrorMessage;
    case FileErrorCode::kNotReadableErr:
      return kNotReadableErrorMessage;
    case FileErrorCode::kEncodingErr:
      return kEncodingErrorMessage;
    case FileErrorCode::kNoModificationAllowedErr:
      return kNoModificationAllowedErrorMessage;
    case FileErrorCode::kInvalidStateErr:
      return kInvalidStateErrorMessage;
    case FileErrorCode::kSyntaxErr:
      return kSyntaxErrorMessage;
    case FileErrorCode::kInvalidModificationErr:
      return nullptr;
    case FileErrorCode::kQuotaExceededErr:
      return kQuotaExceededErrorMessage;
    case FileErrorCode::kTypeMismatchErr:
      return nullptr;
    case FileErrorCode::kPathExistsErr:
      return kPathExistsErrorMessage;
    default:
      NOTREACHED();
  }
}

DOMExceptionCode FileErrorToExceptionCode(base::File::Error code) {
  switch (code) {
    case base::File::FILE_OK:
      return DOMExceptionCode::kNoError;
    case base::File::FILE_ERROR_FAILED:
      return DOMExceptionCode::kInvalidStateError;
    // TODO(https://crbug.com/883062): base::File::FILE_ERROR_EXISTS should map
    // to kPathExistsError, but that currently breaks tests. Fix the test
    // expectations and make the change.
    case base::File::FILE_ERROR_EXISTS:
    case base::File::FILE_ERROR_NOT_EMPTY:
    case base::File::FILE_ERROR_INVALID_OPERATION:
      return DOMExceptionCode::kInvalidModificationError;
    case base::File::FILE_ERROR_TOO_MANY_OPENED:
    case base::File::FILE_ERROR_NO_MEMORY:
      return DOMExceptionCode::kUnknownError;
    case base::File::FILE_ERROR_NOT_FOUND:
      return DOMExceptionCode::kNotFoundError;
    case base::File::FILE_ERROR_IN_USE:
    case base::File::FILE_ERROR_ACCESS_DENIED:
      return DOMExceptionCode::kNoModificationAllowedError;
    case base::File::FILE_ERROR_NO_SPACE:
      return DOMExceptionCode::kQuotaExceededError;
    case base::File::FILE_ERROR_NOT_A_DIRECTORY:
    case base::File::FILE_ERROR_NOT_A_FILE:
      return DOMExceptionCode::kTypeMismatchError;
    case base::File::FILE_ERROR_ABORT:
      return DOMExceptionCode::kAbortError;
    case base::File::FILE_ERROR_SECURITY:
      return DOMExceptionCode::kSecurityError;
    case base::File::FILE_ERROR_INVALID_URL:
      return DOMExceptionCode::kEncodingError;
    case base::File::FILE_ERROR_IO:
      return DOMExceptionCode::kNotReadableError;
    case base::File::FILE_ERROR_MAX:
      NOTREACHED();
  }
  NOTREACHED();
}

const char* FileErrorToMessage(base::File::Error code) {
  // Note that some of these do not set message. If message is null then the
  // default message is used.
  switch (code) {
    case base::File::FILE_ERROR_NOT_FOUND:
      return kNotFoundErrorMessage;
    case base::File::FILE_ERROR_ACCESS_DENIED:
      return kNoModificationAllowedErrorMessage;
    case base::File::FILE_ERROR_FAILED:
      return kInvalidStateErrorMessage;
    case base::File::FILE_ERROR_ABORT:
      return kAbortErrorMessage;
    case base::File::FILE_ERROR_SECURITY:
      return kSecurityErrorMessage;
    case base::File::FILE_ERROR_NO_SPACE:
      return kQuotaExceededErrorMessage;
    case base::File::FILE_ERROR_INVALID_URL:
      return kEncodingErrorMessage;
    case base::File::FILE_ERROR_IO:
      return kNotReadableErrorMessage;
    case base::File::FILE_ERROR_EXISTS:
      return kPathExistsErrorMessage;
    case base::File::FILE_ERROR_NOT_A_DIRECTORY:
    case base::File::FILE_ERROR_NOT_A_FILE:
      return kTypeMismatchErrorMessage;
    case base::File::FILE_OK:
    case base::File::FILE_ERROR_INVALID_OPERATION:
    case base::File::FILE_ERROR_NOT_EMPTY:
    case base::File::FILE_ERROR_NO_MEMORY:
    case base::File::FILE_ERROR_TOO_MANY_OPENED:
    case base::File::FILE_ERROR_IN_USE:
      // TODO(mek): More specific error messages for at least some of these
      // errors.
      return nullptr;
    case base::File::FILE_ERROR_MAX:
      NOTREACHED();
  }
  NOTREACHED();
}

}  // namespace

void ThrowDOMException(ExceptionState& exception_state,
                       FileErrorCode code,
                       String message) {
  if (code == FileErrorCode::kOK)
    return;

  // SecurityError is special-cased, as we want to route those exceptions
  // through ExceptionState::ThrowSecurityError.
  if (code == FileErrorCode::kSecurityErr) {
    exception_state.ThrowSecurityError(kSecurityErrorMessage);
    return;
  }

  if (message.IsNull()) {
    message = ErrorCodeToMessage(code);
  }

  DOMExceptionCode exception_code = ErrorCodeToExceptionCode(code);
  if (exception_code == DOMExceptionCode::kQuotaExceededError) {
    QuotaExceededError::Throw(exception_state, message);
    return;
  }
  exception_state.ThrowDOMException(exception_code, message);
}

void ThrowDOMException(ExceptionState& exception_state,
                       base::File::Error error,
                       String message) {
  if (error == base::File::FILE_OK)
    return;

  // SecurityError is special-cased, as we want to route those exceptions
  // through ExceptionState::ThrowSecurityError.
  if (error == base::File::FILE_ERROR_SECURITY) {
    exception_state.ThrowSecurityError(kSecurityErrorMessage);
    return;
  }

  if (message.IsNull()) {
    message = FileErrorToMessage(error);
  }

  DOMExceptionCode exception_code = FileErrorToExceptionCode(error);
  if (exception_code == DOMExceptionCode::kQuotaExceededError) {
    QuotaExceededError::Throw(exception_state, message);
    return;
  }
  exception_state.ThrowDOMException(exception_code, message);
}

DOMException* CreateDOMException(FileErrorCode code) {
  DCHECK_NE(code, FileErrorCode::kOK);
  DOMExceptionCode exception_code = ErrorCodeToExceptionCode(code);
  String message = ErrorCodeToMessage(code);

  if (exception_code == DOMExceptionCode::kQuotaExceededError &&
      RuntimeEnabledFeatures::QuotaExceededErrorUpdateEnabled()) {
    return QuotaExceededError::Create(
        message, MakeGarbageCollected<QuotaExceededErrorOptions>());
  }
  return MakeGarbageCollected<DOMException>(exception_code, message);
}

DOMException* CreateDOMException(base::File::Error code) {
  DCHECK_NE(code, base::File::FILE_OK);
  DOMExceptionCode exception_code = FileErrorToExceptionCode(code);
  String message = FileErrorToMessage(code);

  if (exception_code == DOMExceptionCode::kQuotaExceededError &&
      RuntimeEnabledFeatures::QuotaExceededErrorUpdateEnabled()) {
    return QuotaExceededError::Create(
        message, MakeGarbageCollected<QuotaExceededErrorOptions>());
  }
  return MakeGarbageCollected<DOMException>(exception_code, message);
}

}  // namespace file_error

}  // namespace blink
