// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/renderer/bindings/api_signature.h"

#include <algorithm>

#include "base/memory/raw_ref.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/renderer/bindings/api_binding_util.h"
#include "extensions/renderer/bindings/api_invocation_errors.h"
#include "extensions/renderer/bindings/argument_spec.h"
#include "gin/arguments.h"

namespace extensions {

namespace {

std::vector<std::unique_ptr<ArgumentSpec>> ValueListToArgumentSpecs(
    const base::ListValue& specification_list,
    bool uses_returns_async) {
  std::vector<std::unique_ptr<ArgumentSpec>> signatures;
  auto size = specification_list.size();
  // If the API specification uses the returns_async format we will be pushing a
  // callback onto the end of the argument spec list during the call to the ctor
  // later, so we make room for it now when we reserve the size.
  if (uses_returns_async)
    size++;
  signatures.reserve(size);
  for (const auto& signature : specification_list) {
    signatures.push_back(std::make_unique<ArgumentSpec>(signature.GetDict()));
  }

  return signatures;
}

std::unique_ptr<APISignature::ReturnsAsync> BuildReturnsAsyncFromValues(
    const base::DictValue& returns_async_spec) {
  auto returns_async = std::make_unique<APISignature::ReturnsAsync>();

  // We only need to know if the callback is required for functions which don't
  // support Promise-based calls, otherwise we ignore what the schema says and
  // treat them as optional because callbacks are omitted when using
  // Promise-based APIs.
  if (returns_async_spec.Find("does_not_support_promises")) {
    returns_async->promise_support = binding::APIPromiseSupport::kUnsupported;
    returns_async->optional =
        returns_async_spec.FindBool("optional").value_or(false);
  } else {
    returns_async->promise_support = binding::APIPromiseSupport::kSupported;
    returns_async->optional = true;
  }

  // If response validation is enabled, parse the callback signature. Otherwise,
  // there's no reason to, so don't bother.
  if (binding::IsResponseValidationEnabled()) {
    const base::ListValue* callback_params =
        returns_async_spec.FindList("parameters");
    if (callback_params) {
      returns_async->signature =
          ValueListToArgumentSpecs(*callback_params, false);
    }
  }
  return returns_async;
}

std::string ArgumentSpecsToString(
    const std::vector<std::unique_ptr<ArgumentSpec>>& argument_specs) {
  std::vector<std::string> pieces;
  pieces.reserve(argument_specs.size());
  const char* kOptionalPrefix = "optional ";
  for (const auto& spec : argument_specs) {
    pieces.push_back(
        base::StringPrintf("%s%s %s", spec->optional() ? kOptionalPrefix : "",
                           spec->GetTypeName().c_str(), spec->name().c_str()));
  }
  return base::JoinString(pieces, ", ");
}

// A class to help with argument parsing. Note that this uses v8::Locals and
// const&s because it's an implementation detail of the APISignature; this
// should *only* be used directly on the stack!
class ArgumentParser {
 public:
  ArgumentParser(v8::Local<v8::Context> context,
                 const std::vector<std::unique_ptr<ArgumentSpec>>& signature,
                 const v8::LocalVector<v8::Value>& arguments,
                 const APITypeReferenceMap& type_refs,
                 binding::APIPromiseSupport promise_support)
      : context_(context),
        signature_(signature),
        provided_arguments_(arguments),
        type_refs_(type_refs),
        promise_support_(promise_support) {}

  ArgumentParser(const ArgumentParser&) = delete;
  ArgumentParser& operator=(const ArgumentParser&) = delete;

 protected:
  v8::Isolate* GetIsolate() { return v8::Isolate::GetCurrent(); }

  // Common implementation for parsing arguments to either V8 values or
  // base::Values.
  bool ParseArgumentsImpl(bool signature_has_callback);

  std::string TakeError() { return std::move(error_); }
  binding::AsyncResponseType async_type() const { return async_type_; }

 private:
  // API methods can have multiple possible signatures. For instance, an API
  // method that takes (optional int, string) could be invoked with either
  // an int and string, or just a string. ResolveArguments() takes the
  // |provided| arguments and the |expected| signature, and populates |result|
  // with a normalized array of values such that each entry in |result| is
  // positionally correct with the signature. Omitted arguments will be
  // empty v8::Local<v8::Value> handles in the array.
  // Returns true if the arguments were successfully resolved.
  // Note: This only checks arguments against their basic types, not other
  // values (like specific required properties or values).
  bool ResolveArguments(
      base::span<const v8::Local<v8::Value>> provided,
      base::span<const std::unique_ptr<ArgumentSpec>> expected,
      v8::LocalVector<v8::Value>* result,
      size_t index);

  // Attempts to match the next argument to the given |spec|.
  // If the next argument does not match and |spec| is optional, uses a null
  // value.
  // Returns true on success.
  bool ParseArgument(const ArgumentSpec& spec, v8::Local<v8::Value> value);

  // Attempts to parse the callback from the given |spec|. Returns true on
  // success.
  bool ParseCallback(const ArgumentSpec& spec, v8::Local<v8::Value> value);

  // Adds a null value to the parsed arguments.
  virtual void AddNull() = 0;
  virtual void AddNullCallback() = 0;
  // Returns a base::Value to be populated during argument matching.
  virtual std::unique_ptr<base::Value>* GetBaseBuffer() = 0;
  // Returns a v8::Value to be populated during argument matching.
  virtual v8::Local<v8::Value>* GetV8Buffer() = 0;
  // Adds the argument parsed into the appropriate buffer.
  virtual void AddParsedArgument() = 0;
  // Adds the parsed callback.
  virtual void SetCallback(v8::Local<v8::Function> callback) = 0;

  v8::Local<v8::Context> context_;
  const raw_ref<const std::vector<std::unique_ptr<ArgumentSpec>>> signature_;
  const raw_ref<const v8::LocalVector<v8::Value>> provided_arguments_;
  const raw_ref<const APITypeReferenceMap> type_refs_;
  binding::APIPromiseSupport promise_support_;
  binding::AsyncResponseType async_type_ = binding::AsyncResponseType::kNone;
  std::string error_;

  // An error to pass while parsing arguments to avoid having to allocate a new
  // std::string on the stack multiple times.
  std::string parse_error_;
};

class V8ArgumentParser : public ArgumentParser {
 public:
  V8ArgumentParser(v8::Local<v8::Context> context,
                   const std::vector<std::unique_ptr<ArgumentSpec>>& signature,
                   const v8::LocalVector<v8::Value>& arguments,
                   const APITypeReferenceMap& type_refs,
                   binding::APIPromiseSupport promise_support)
      : ArgumentParser(context,
                       signature,
                       arguments,
                       type_refs,
                       promise_support),
        values_(v8::Isolate::GetCurrent()) {}

  V8ArgumentParser(const V8ArgumentParser&) = delete;
  V8ArgumentParser& operator=(const V8ArgumentParser&) = delete;

  APISignature::V8ParseResult ParseArguments(bool signature_has_callback);

 private:
  void AddNull() override { values_.push_back(v8::Null(GetIsolate())); }
  void AddNullCallback() override { values_.push_back(v8::Null(GetIsolate())); }
  std::unique_ptr<base::Value>* GetBaseBuffer() override { return nullptr; }
  v8::Local<v8::Value>* GetV8Buffer() override { return &last_arg_; }
  void AddParsedArgument() override {
    DCHECK(!last_arg_.IsEmpty());
    values_.push_back(last_arg_);
    last_arg_.Clear();
  }
  void SetCallback(v8::Local<v8::Function> callback) override {
    values_.push_back(callback);
  }

  v8::Local<v8::Value> last_arg_;
  v8::LocalVector<v8::Value> values_;
};

class BaseValueArgumentParser : public ArgumentParser {
 public:
  BaseValueArgumentParser(
      v8::Local<v8::Context> context,
      const std::vector<std::unique_ptr<ArgumentSpec>>& signature,
      const v8::LocalVector<v8::Value>& arguments,
      const APITypeReferenceMap& type_refs,
      binding::APIPromiseSupport promise_support)
      : ArgumentParser(context,
                       signature,
                       arguments,
                       type_refs,
                       promise_support) {}

  BaseValueArgumentParser(const BaseValueArgumentParser&) = delete;
  BaseValueArgumentParser& operator=(const BaseValueArgumentParser&) = delete;

  APISignature::JSONParseResult ParseArguments(bool signature_has_callback);

 private:
  void AddNull() override { list_value_.Append(base::Value()); }
  void AddNullCallback() override {
    // The base::Value conversion doesn't include the callback directly, so we
    // don't add a null parameter here.
  }
  std::unique_ptr<base::Value>* GetBaseBuffer() override { return &last_arg_; }
  v8::Local<v8::Value>* GetV8Buffer() override { return nullptr; }
  void AddParsedArgument() override {
    // The corresponding base::Value is expected to have been stored in
    // |last_arg_| already.
    DCHECK(last_arg_);
    list_value_.Append(base::Value::FromUniquePtrValue(std::move(last_arg_)));
  }
  void SetCallback(v8::Local<v8::Function> callback) override {
    callback_ = callback;
  }

  base::ListValue list_value_;
  std::unique_ptr<base::Value> last_arg_;
  v8::Local<v8::Function> callback_;
};

bool ArgumentParser::ParseArgumentsImpl(bool signature_has_callback) {
  if (provided_arguments_->size() > signature_->size()) {
    error_ = api_errors::NoMatchingSignature();
    return false;
  }

  v8::LocalVector<v8::Value> resolved_arguments(v8::Isolate::GetCurrent(),
                                                signature_->size());
  if (!ResolveArguments(*provided_arguments_, *signature_, &resolved_arguments,
                        0u)) {
    error_ = api_errors::NoMatchingSignature();
    return false;
  }
  DCHECK_EQ(resolved_arguments.size(), signature_->size());

  size_t end_size =
      signature_has_callback ? signature_->size() - 1 : signature_->size();
  for (size_t i = 0; i < end_size; ++i) {
    if (!ParseArgument(*(*signature_)[i], resolved_arguments[i])) {
      return false;
    }
  }

  if (signature_has_callback &&
      !ParseCallback(*signature_->back(), resolved_arguments.back())) {
    return false;
  }

  return true;
}

bool ArgumentParser::ResolveArguments(
    base::span<const v8::Local<v8::Value>> provided,
    base::span<const std::unique_ptr<ArgumentSpec>> expected,
    v8::LocalVector<v8::Value>* result,
    size_t index) {
  // If the provided arguments and expected arguments are both empty, it means
  // we've successfully matched all provided arguments to the expected
  // signature.
  if (provided.empty() && expected.empty())
    return true;

  // If there are more provided arguments than expected arguments, there's no
  // possible signature that could match.
  if (provided.size() > expected.size())
    return false;

  // If there are more provided arguments (and more expected arguments, as
  // guaranteed above), check if the next argument could match the next expected
  // argument.
  if (!provided.empty()) {
    // The argument could potentially match if it is either null or undefined
    // and an optional argument, or if it's the correct expected type.
    bool can_match = false;
    if (expected[0]->optional() && provided[0]->IsNullOrUndefined()) {
      can_match = true;
      // For null/undefined, just use an empty handle. It'll be normalized to
      // null in ParseArgument().
      (*result)[index] = v8::Local<v8::Value>();
    } else if (expected[0]->IsCorrectType(provided[0], *type_refs_, &error_)) {
      can_match = true;
      (*result)[index] = provided[0];
    }

    // If the provided argument could potentially match the next expected
    // argument, assume it does, and try to match the remaining arguments.
    // This recursion is safe because it's bounded by the number of arguments
    // present in the signature. Additionally, though this is 2^n complexity,
    // <n> is bounded by the number of expected arguments, which is almost
    // always small. Further, it is only when parameters are optional, which is
    // also not the default.
    if (can_match &&
        ResolveArguments(provided.subspan<1>(), expected.subspan<1>(), result,
                         index + 1)) {
      return true;
    }
  }

  // One of three cases happened:
  // - There are no more provided arguments.
  // - The next provided argument could not match the expected argument.
  // - The next provided argument could match the expected argument, but
  //   subsequent arguments did not.
  // In all of these cases, if the expected argument was optional, assume it
  // was omitted, and try matching subsequent arguments.
  if (expected[0]->optional()) {
    // Assume the expected argument was omitted.
    (*result)[index] = v8::Local<v8::Value>();
    // See comments above for recursion notes.
    if (ResolveArguments(provided, expected.subspan<1>(), result, index + 1)) {
      return true;
    }
  }

  // A required argument was not matched.
  return false;
}

bool ArgumentParser::ParseArgument(const ArgumentSpec& spec,
                                   v8::Local<v8::Value> value) {
  if (value.IsEmpty()) {
    // ResolveArguments() should only allow empty values for optional arguments.
    DCHECK(spec.optional());
    AddNull();
    return true;
  }

  // ResolveArguments() should verify that all arguments are at least the
  // correct type.
  DCHECK(spec.IsCorrectType(value, *type_refs_, &error_));
  if (!spec.ParseArgument(context_, value, *type_refs_, GetBaseBuffer(),
                          GetV8Buffer(), &parse_error_)) {
    error_ = api_errors::ArgumentError(spec.name(), parse_error_);
    return false;
  }

  AddParsedArgument();
  return true;
}

bool ArgumentParser::ParseCallback(const ArgumentSpec& spec,
                                   v8::Local<v8::Value> value) {
  if (value.IsEmpty()) {
    // Note: The null callback isn't exactly correct. See
    // https://crbug.com/1220910 for details.
    AddNullCallback();

    if (promise_support_ == binding::APIPromiseSupport::kSupported) {
      // If the callback is omitted and promises are supported, assume the
      // async response type is a promise.
      async_type_ = binding::AsyncResponseType::kPromise;
    } else {
      // Otherwise, we should only get to this point if the callback argument is
      // optional.
      DCHECK(spec.optional());
      async_type_ = binding::AsyncResponseType::kNone;
    }
    return true;
  }

  // Note: callbacks are set through SetCallback() rather than through the
  // buffered argument.
  if (!spec.ParseArgument(context_, value, *type_refs_, nullptr, nullptr,
                          &parse_error_)) {
    error_ = api_errors::ArgumentError(spec.name(), parse_error_);
    return false;
  }

  SetCallback(value.As<v8::Function>());
  async_type_ = binding::AsyncResponseType::kCallback;
  return true;
}

APISignature::V8ParseResult V8ArgumentParser::ParseArguments(
    bool signature_has_callback) {
  APISignature::V8ParseResult result;
  if (!ParseArgumentsImpl(signature_has_callback)) {
    result.error = TakeError();
  } else {
    result.arguments = std::move(values_);
    result.async_type = async_type();
  }

  return result;
}

APISignature::JSONParseResult BaseValueArgumentParser::ParseArguments(
    bool signature_has_callback) {
  APISignature::JSONParseResult result;
  if (!ParseArgumentsImpl(signature_has_callback)) {
    result.error = TakeError();
  } else {
    result.arguments_list = std::move(list_value_);
    result.callback = callback_;
    result.async_type = async_type();
  }
  return result;
}

// A helper method used to validate a signature for an internal caller (such as
// a response to an API method or event arguments) to ensure it matches the
// expected schema.
bool ValidateSignatureForInternalCaller(
    v8::Local<v8::Context> context,
    const v8::LocalVector<v8::Value>& arguments,
    const std::vector<std::unique_ptr<ArgumentSpec>>& expected,
    const APITypeReferenceMap& type_refs,
    std::string* error) {
  size_t expected_size = expected.size();
  size_t actual_size = arguments.size();
  if (actual_size > expected_size) {
    *error = api_errors::TooManyArguments();
    return false;
  }

  // Easy validation: arguments go in order, and must match the expected schema.
  // Anything less is failure.
  std::string parse_error;
  for (size_t i = 0; i < actual_size; ++i) {
    DCHECK(!arguments[i].IsEmpty());
    const ArgumentSpec& spec = *expected[i];
    if (arguments[i]->IsNullOrUndefined()) {
      if (!spec.optional()) {
        *error = api_errors::MissingRequiredArgument(spec.name().c_str());
        return false;
      }
      continue;
    }

    if (!spec.ParseArgument(context, arguments[i], type_refs, nullptr, nullptr,
                            &parse_error)) {
      *error = api_errors::ArgumentError(spec.name(), parse_error);
      return false;
    }
  }

  // Responses may omit trailing optional parameters (which would then be
  // undefined for the caller).
  // NOTE(devlin): It might be nice to see if we could require all arguments to
  // be present, no matter what. For one, it avoids this loop, and it would also
  // unify what a "not found" value was (some APIs use undefined, some use
  // null).
  for (size_t i = actual_size; i < expected_size; ++i) {
    if (!expected[i]->optional()) {
      *error = api_errors::MissingRequiredArgument(expected[i]->name().c_str());
      return false;
    }
  }

  return true;
}

}  // namespace

APISignature::ReturnsAsync::ReturnsAsync() = default;
APISignature::ReturnsAsync::~ReturnsAsync() = default;

APISignature::V8ParseResult::V8ParseResult() = default;
APISignature::V8ParseResult::~V8ParseResult() = default;
APISignature::V8ParseResult::V8ParseResult(V8ParseResult&& other) = default;
APISignature::V8ParseResult& APISignature::V8ParseResult::operator=(
    V8ParseResult&& other) = default;

APISignature::JSONParseResult::JSONParseResult() = default;
APISignature::JSONParseResult::~JSONParseResult() = default;
APISignature::JSONParseResult::JSONParseResult(JSONParseResult&& other) =
    default;
APISignature::JSONParseResult& APISignature::JSONParseResult::operator=(
    JSONParseResult&& other) = default;

APISignature::APISignature(
    std::vector<std::unique_ptr<ArgumentSpec>> signature,
    std::unique_ptr<APISignature::ReturnsAsync> returns_async)
    : signature_(std::move(signature)),
      returns_async_(std::move(returns_async)) {
  if (returns_async_) {
    // TODO(tjudkins): Argument parsing during an API call currently expects any
    // potential callback to be part of the list of expected arguments, rather
    // than represented separately in the ReturnsAsync struct. It would be nice
    // to update that code to know about the ReturnsAsync struct instead.
    // That would also avoid the slightly-inefficient "dance" we have for APIs
    // that don't specify returns_async today, since we currently pop the
    // callback in CreateFromValues() and then re-add it here.
    auto callback = std::make_unique<ArgumentSpec>(ArgumentType::FUNCTION);
    callback->set_optional(returns_async_->optional);
    callback->set_name("callback");
    signature_.push_back(std::move(callback));
  }
}

APISignature::~APISignature() = default;

// static
std::unique_ptr<APISignature> APISignature::CreateFromValues(
    const base::Value& spec_list,
    const base::Value* returns_async) {
  bool uses_returns_async = returns_async != nullptr;
  auto argument_specs =
      ValueListToArgumentSpecs(spec_list.GetList(), uses_returns_async);

  // Asynchronous returns for an API are defined in the returns_async part of
  // the specification.
  std::unique_ptr<APISignature::ReturnsAsync> returns_async_struct;
  if (returns_async) {
    returns_async_struct =
        BuildReturnsAsyncFromValues(returns_async->GetDict());
  }

  return std::make_unique<APISignature>(std::move(argument_specs),
                                        std::move(returns_async_struct));
}

APISignature::V8ParseResult APISignature::ParseArgumentsToV8(
    v8::Local<v8::Context> context,
    const v8::LocalVector<v8::Value>& arguments,
    const APITypeReferenceMap& type_refs) const {
  return V8ArgumentParser(context, signature_, arguments, type_refs,
                          api_promise_support())
      .ParseArguments(has_async_return());
}

APISignature::JSONParseResult APISignature::ParseArgumentsToJSON(
    v8::Local<v8::Context> context,
    const v8::LocalVector<v8::Value>& arguments,
    const APITypeReferenceMap& type_refs) const {
  return BaseValueArgumentParser(context, signature_, arguments, type_refs,
                                 api_promise_support())
      .ParseArguments(has_async_return());
}

APISignature::JSONParseResult APISignature::ConvertArgumentsIgnoringSchema(
    v8::Local<v8::Context> context,
    const v8::LocalVector<v8::Value>& arguments) const {
  JSONParseResult result;
  size_t size = arguments.size();
  // TODO(devlin): This is what the current bindings do, but it's quite terribly
  // incorrect. We only hit this flow when an API method has a hook to update
  // the arguments post-validation, and in some cases, the arguments returned by
  // that hook do *not* match the signature of the API method (e.g.
  // fileSystem.getDisplayPath); see also note in api_bindings.cc for why this
  // is bad. But then here, we *rely* on the signature to determine whether or
  // not the last parameter is a callback, even though the hooks may not return
  // the arguments in the signature. This is very broken.
  if (has_async_return()) {
    CHECK(!arguments.empty());
    v8::Local<v8::Value> value = arguments.back();
    --size;
    // Bindings should ensure that the value here is appropriate, but see the
    // comment above for limitations.
    DCHECK(value->IsFunction() || value->IsUndefined() || value->IsNull());
    if (value->IsFunction()) {
      result.callback = value.As<v8::Function>();
      result.async_type = binding::AsyncResponseType::kCallback;
    } else if ((value->IsNull() || value->IsUndefined()) &&
               api_promise_support() ==
                   binding::APIPromiseSupport::kSupported) {
      result.async_type = binding::AsyncResponseType::kPromise;
    }
  }

  base::ListValue json;
  json.reserve(size);

  std::unique_ptr<content::V8ValueConverter> converter =
      content::V8ValueConverter::Create();
  converter->SetFunctionAllowed(true);
  converter->SetConvertNegativeZeroToInt(true);
  converter->SetStripNullFromObjects(true);

  for (size_t i = 0; i < size; ++i) {
    std::unique_ptr<base::Value> converted =
        converter->FromV8Value(arguments[i], context);
    if (!converted) {
      // JSON.stringify inserts non-serializable values as "null" when
      // handling arrays, and this behavior is emulated in V8ValueConverter for
      // array values. Since JS bindings parsed arguments from a single array,
      // it accepted unserializable argument entries (which were converted to
      // null). Duplicate that behavior here.
      converted = std::make_unique<base::Value>();
    }
    json.Append(base::Value::FromUniquePtrValue(std::move(converted)));
  }

  result.arguments_list = std::move(json);
  return result;
}

bool APISignature::ValidateResponse(v8::Local<v8::Context> context,
                                    const v8::LocalVector<v8::Value>& arguments,
                                    const APITypeReferenceMap& type_refs,
                                    std::string* error) const {
  DCHECK(returns_async_);
  DCHECK(returns_async_->signature);
  return ValidateSignatureForInternalCaller(
      context, arguments, *returns_async_->signature, type_refs, error);
}

bool APISignature::ValidateCall(v8::Local<v8::Context> context,
                                const v8::LocalVector<v8::Value>& arguments,
                                const APITypeReferenceMap& type_refs,
                                std::string* error) const {
  return ValidateSignatureForInternalCaller(context, arguments, signature_,
                                            type_refs, error);
}

std::string APISignature::GetExpectedSignature() const {
  if (!expected_signature_.empty() || signature_.empty())
    return expected_signature_;

  expected_signature_ = ArgumentSpecsToString(signature_);

  return expected_signature_;
}

}  // namespace extensions
