// Copyright 2016 The Chromium Authors. All rights reserved. // 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 #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_invocation_errors.h" #include "extensions/renderer/bindings/argument_spec.h" #include "gin/arguments.h" namespace extensions { namespace { bool HasCallback(const std::vector>& signature) { // TODO(devlin): This is how extension APIs have always determined if a // function has a callback, but it seems a little silly. In the long run (once // signatures are generated), it probably makes sense to indicate this // differently. return !signature.empty() && signature.back()->type() == ArgumentType::FUNCTION; } // 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 context, const std::vector>& signature, const std::vector>& arguments, const APITypeReferenceMap& type_refs, std::string* error) : context_(context), signature_(signature), provided_arguments_(arguments), type_refs_(type_refs), error_(error) {} // Tries to parse the arguments against the expected signature. bool ParseArguments(); protected: v8::Isolate* GetIsolate() { return context_->GetIsolate(); } 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 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> provided, base::span> expected, std::vector>* 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 value); // Attempts to parse the callback from the given |spec|. Returns true on // success. bool ParseCallback(const ArgumentSpec& spec, v8::Local 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* GetBaseBuffer() = 0; // Returns a v8::Value to be populated during argument matching. virtual v8::Local* GetV8Buffer() = 0; // Adds the argument parsed into the appropriate buffer. virtual void AddParsedArgument() = 0; // Adds the parsed callback. virtual void SetCallback(v8::Local callback) = 0; v8::Local context_; const std::vector>& signature_; const std::vector>& provided_arguments_; const APITypeReferenceMap& type_refs_; 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_; DISALLOW_COPY_AND_ASSIGN(ArgumentParser); }; class V8ArgumentParser : public ArgumentParser { public: V8ArgumentParser(v8::Local context, const std::vector>& signature, const std::vector>& arguments, const APITypeReferenceMap& type_refs, std::string* error, std::vector>* values) : ArgumentParser(context, signature, arguments, type_refs, error), values_(values) {} private: void AddNull() override { values_->push_back(v8::Null(GetIsolate())); } void AddNullCallback() override { values_->push_back(v8::Null(GetIsolate())); } std::unique_ptr* GetBaseBuffer() override { return nullptr; } v8::Local* GetV8Buffer() override { return &last_arg_; } void AddParsedArgument() override { DCHECK(!last_arg_.IsEmpty()); values_->push_back(last_arg_); last_arg_.Clear(); } void SetCallback(v8::Local callback) override { values_->push_back(callback); } v8::Local last_arg_; std::vector>* values_; DISALLOW_COPY_AND_ASSIGN(V8ArgumentParser); }; class BaseValueArgumentParser : public ArgumentParser { public: BaseValueArgumentParser( v8::Local context, const std::vector>& signature, const std::vector>& arguments, const APITypeReferenceMap& type_refs, std::string* error, base::ListValue* list_value) : ArgumentParser(context, signature, arguments, type_refs, error), list_value_(list_value) {} v8::Local callback() { return callback_; } private: void AddNull() override { list_value_->Append(std::make_unique()); } 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* GetBaseBuffer() override { return &last_arg_; } v8::Local* 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(std::move(last_arg_)); last_arg_.reset(); } void SetCallback(v8::Local callback) override { callback_ = callback; } base::ListValue* list_value_; std::unique_ptr last_arg_; v8::Local callback_; DISALLOW_COPY_AND_ASSIGN(BaseValueArgumentParser); }; bool ArgumentParser::ParseArguments() { if (provided_arguments_.size() > signature_.size()) { *error_ = api_errors::NoMatchingSignature(); return false; } std::vector> resolved_arguments(signature_.size()); if (!ResolveArguments(provided_arguments_, signature_, &resolved_arguments, 0u)) { *error_ = api_errors::NoMatchingSignature(); return false; } DCHECK_EQ(resolved_arguments.size(), signature_.size()); bool signature_has_callback = HasCallback(signature_); 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> provided, base::span> expected, std::vector>* 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; DCHECK(!expected.empty()); // 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(); } 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, // 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(); // 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 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 value) { if (value.IsEmpty()) { // ResolveArguments() should only allow empty values for optional arguments. DCHECK(spec.optional()); AddNullCallback(); 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()); return true; } } // namespace APISignature::APISignature(const base::ListValue& specification) { signature_.reserve(specification.GetSize()); for (const auto& value : specification) { const base::DictionaryValue* param = nullptr; CHECK(value.GetAsDictionary(¶m)); signature_.push_back(std::make_unique(*param)); } } APISignature::APISignature(std::vector> signature) : signature_(std::move(signature)) {} APISignature::~APISignature() {} bool APISignature::ParseArgumentsToV8( v8::Local context, const std::vector>& arguments, const APITypeReferenceMap& type_refs, std::vector>* v8_out, std::string* error) const { DCHECK(v8_out); std::vector> v8_values; V8ArgumentParser parser( context, signature_, arguments, type_refs, error, &v8_values); if (!parser.ParseArguments()) return false; *v8_out = std::move(v8_values); return true; } bool APISignature::ParseArgumentsToJSON( v8::Local context, const std::vector>& arguments, const APITypeReferenceMap& type_refs, std::unique_ptr* json_out, v8::Local* callback_out, std::string* error) const { DCHECK(json_out); DCHECK(callback_out); std::unique_ptr json = std::make_unique(); BaseValueArgumentParser parser( context, signature_, arguments, type_refs, error, json.get()); if (!parser.ParseArguments()) return false; *json_out = std::move(json); *callback_out = parser.callback(); return true; } bool APISignature::ConvertArgumentsIgnoringSchema( v8::Local context, const std::vector>& arguments, std::unique_ptr* json_out, v8::Local* callback_out) const { size_t size = arguments.size(); v8::Local callback; // 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 (HasCallback(signature_)) { CHECK(!arguments.empty()); v8::Local 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()) callback = value.As(); } auto json = std::make_unique(); json->Reserve(size); std::unique_ptr converter = content::V8ValueConverter::Create(); converter->SetFunctionAllowed(true); converter->SetConvertNegativeZeroToInt(true); converter->SetStripNullFromObjects(true); for (size_t i = 0; i < size; ++i) { std::unique_ptr converted = converter->FromV8Value(arguments[i], context); if (!converted) return false; json->Append(std::move(converted)); } *json_out = std::move(json); *callback_out = callback; return true; } std::string APISignature::GetExpectedSignature() const { if (!expected_signature_.empty() || signature_.empty()) return expected_signature_; std::vector pieces; pieces.reserve(signature_.size()); const char* kOptionalPrefix = "optional "; for (const auto& spec : signature_) { pieces.push_back( base::StringPrintf("%s%s %s", spec->optional() ? kOptionalPrefix : "", spec->GetTypeName().c_str(), spec->name().c_str())); } expected_signature_ = base::JoinString(pieces, ", "); return expected_signature_; } } // namespace extensions