| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 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 "extensions/renderer/argument_spec.h" | |
| 6 | |
| 7 #include "base/memory/ptr_util.h" | |
| 8 #include "base/strings/string_piece.h" | |
| 9 #include "base/strings/string_util.h" | |
| 10 #include "base/strings/stringprintf.h" | |
| 11 #include "base/values.h" | |
| 12 #include "content/public/child/v8_value_converter.h" | |
| 13 #include "extensions/renderer/api_invocation_errors.h" | |
| 14 #include "extensions/renderer/api_type_reference_map.h" | |
| 15 #include "gin/converter.h" | |
| 16 #include "gin/dictionary.h" | |
| 17 | |
| 18 namespace extensions { | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 // Returns a type string for the given |value|. | |
| 23 const char* GetV8ValueTypeString(v8::Local<v8::Value> value) { | |
| 24 DCHECK(!value.IsEmpty()); | |
| 25 | |
| 26 if (value->IsNull()) | |
| 27 return api_errors::kTypeNull; | |
| 28 if (value->IsUndefined()) | |
| 29 return api_errors::kTypeUndefined; | |
| 30 if (value->IsInt32()) | |
| 31 return api_errors::kTypeInteger; | |
| 32 if (value->IsNumber()) | |
| 33 return api_errors::kTypeDouble; | |
| 34 if (value->IsBoolean()) | |
| 35 return api_errors::kTypeBoolean; | |
| 36 if (value->IsString()) | |
| 37 return api_errors::kTypeString; | |
| 38 | |
| 39 // Note: check IsArray(), IsFunction(), and IsArrayBuffer[View]() before | |
| 40 // IsObject() since arrays, functions, and array buffers are objects. | |
| 41 if (value->IsArray()) | |
| 42 return api_errors::kTypeList; | |
| 43 if (value->IsFunction()) | |
| 44 return api_errors::kTypeFunction; | |
| 45 if (value->IsArrayBuffer() || value->IsArrayBufferView()) | |
| 46 return api_errors::kTypeBinary; | |
| 47 if (value->IsObject()) | |
| 48 return api_errors::kTypeObject; | |
| 49 | |
| 50 // TODO(devlin): The list above isn't exhaustive (it's missing at least | |
| 51 // Symbol and Uint32). We may want to include those, since saying | |
| 52 // "expected int, found other" isn't super helpful. On the other hand, authors | |
| 53 // should be able to see what they passed. | |
| 54 return "other"; | |
| 55 } | |
| 56 | |
| 57 // Returns true if |value| is within the bounds specified by |minimum| and | |
| 58 // |maximum|, populating |error| otherwise. | |
| 59 template <class T> | |
| 60 bool CheckFundamentalBounds(T value, | |
| 61 const base::Optional<int>& minimum, | |
| 62 const base::Optional<int>& maximum, | |
| 63 std::string* error) { | |
| 64 if (minimum && value < *minimum) { | |
| 65 *error = api_errors::NumberTooSmall(*minimum); | |
| 66 return false; | |
| 67 } | |
| 68 if (maximum && value > *maximum) { | |
| 69 *error = api_errors::NumberTooLarge(*maximum); | |
| 70 return false; | |
| 71 } | |
| 72 return true; | |
| 73 } | |
| 74 | |
| 75 } // namespace | |
| 76 | |
| 77 ArgumentSpec::ArgumentSpec(const base::Value& value) { | |
| 78 const base::DictionaryValue* dict = nullptr; | |
| 79 CHECK(value.GetAsDictionary(&dict)); | |
| 80 dict->GetBoolean("optional", &optional_); | |
| 81 dict->GetString("name", &name_); | |
| 82 | |
| 83 InitializeType(dict); | |
| 84 } | |
| 85 | |
| 86 ArgumentSpec::ArgumentSpec(ArgumentType type) : type_(type) {} | |
| 87 | |
| 88 void ArgumentSpec::InitializeType(const base::DictionaryValue* dict) { | |
| 89 std::string ref_string; | |
| 90 if (dict->GetString("$ref", &ref_string)) { | |
| 91 ref_ = std::move(ref_string); | |
| 92 type_ = ArgumentType::REF; | |
| 93 return; | |
| 94 } | |
| 95 | |
| 96 { | |
| 97 const base::ListValue* choices = nullptr; | |
| 98 if (dict->GetList("choices", &choices)) { | |
| 99 DCHECK(!choices->empty()); | |
| 100 type_ = ArgumentType::CHOICES; | |
| 101 choices_.reserve(choices->GetSize()); | |
| 102 for (const auto& choice : *choices) | |
| 103 choices_.push_back(base::MakeUnique<ArgumentSpec>(choice)); | |
| 104 return; | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 std::string type_string; | |
| 109 CHECK(dict->GetString("type", &type_string)); | |
| 110 if (type_string == "integer") | |
| 111 type_ = ArgumentType::INTEGER; | |
| 112 else if (type_string == "number") | |
| 113 type_ = ArgumentType::DOUBLE; | |
| 114 else if (type_string == "object") | |
| 115 type_ = ArgumentType::OBJECT; | |
| 116 else if (type_string == "array") | |
| 117 type_ = ArgumentType::LIST; | |
| 118 else if (type_string == "boolean") | |
| 119 type_ = ArgumentType::BOOLEAN; | |
| 120 else if (type_string == "string") | |
| 121 type_ = ArgumentType::STRING; | |
| 122 else if (type_string == "binary") | |
| 123 type_ = ArgumentType::BINARY; | |
| 124 else if (type_string == "any") | |
| 125 type_ = ArgumentType::ANY; | |
| 126 else if (type_string == "function") | |
| 127 type_ = ArgumentType::FUNCTION; | |
| 128 else | |
| 129 NOTREACHED(); | |
| 130 | |
| 131 int min = 0; | |
| 132 if (dict->GetInteger("minimum", &min)) | |
| 133 minimum_ = min; | |
| 134 | |
| 135 int max = 0; | |
| 136 if (dict->GetInteger("maximum", &max)) | |
| 137 maximum_ = max; | |
| 138 | |
| 139 int min_length = 0; | |
| 140 if (dict->GetInteger("minLength", &min_length) || | |
| 141 dict->GetInteger("minItems", &min_length)) { | |
| 142 DCHECK_GE(min_length, 0); | |
| 143 min_length_ = min_length; | |
| 144 } | |
| 145 | |
| 146 int max_length = 0; | |
| 147 if (dict->GetInteger("maxLength", &max_length) || | |
| 148 dict->GetInteger("maxItems", &max_length)) { | |
| 149 DCHECK_GE(max_length, 0); | |
| 150 max_length_ = max_length; | |
| 151 } | |
| 152 | |
| 153 if (type_ == ArgumentType::OBJECT) { | |
| 154 const base::DictionaryValue* properties_value = nullptr; | |
| 155 if (dict->GetDictionary("properties", &properties_value)) { | |
| 156 for (base::DictionaryValue::Iterator iter(*properties_value); | |
| 157 !iter.IsAtEnd(); iter.Advance()) { | |
| 158 properties_[iter.key()] = base::MakeUnique<ArgumentSpec>(iter.value()); | |
| 159 } | |
| 160 } | |
| 161 const base::DictionaryValue* additional_properties_value = nullptr; | |
| 162 if (dict->GetDictionary("additionalProperties", | |
| 163 &additional_properties_value)) { | |
| 164 additional_properties_ = | |
| 165 base::MakeUnique<ArgumentSpec>(*additional_properties_value); | |
| 166 // Additional properties are always optional. | |
| 167 additional_properties_->optional_ = true; | |
| 168 } | |
| 169 std::string instance_of; | |
| 170 if (dict->GetString("isInstanceOf", &instance_of)) { | |
| 171 instance_of_ = instance_of; | |
| 172 } | |
| 173 } else if (type_ == ArgumentType::LIST) { | |
| 174 const base::DictionaryValue* item_value = nullptr; | |
| 175 CHECK(dict->GetDictionary("items", &item_value)); | |
| 176 list_element_type_ = base::MakeUnique<ArgumentSpec>(*item_value); | |
| 177 } else if (type_ == ArgumentType::STRING) { | |
| 178 // Technically, there's no reason enums couldn't be other objects (e.g. | |
| 179 // numbers), but right now they seem to be exclusively strings. We could | |
| 180 // always update this if need be. | |
| 181 const base::ListValue* enums = nullptr; | |
| 182 if (dict->GetList("enum", &enums)) { | |
| 183 size_t size = enums->GetSize(); | |
| 184 CHECK_GT(size, 0u); | |
| 185 for (size_t i = 0; i < size; ++i) { | |
| 186 std::string enum_value; | |
| 187 // Enum entries come in two versions: a list of possible strings, and | |
| 188 // a dictionary with a field 'name'. | |
| 189 if (!enums->GetString(i, &enum_value)) { | |
| 190 const base::DictionaryValue* enum_value_dictionary = nullptr; | |
| 191 CHECK(enums->GetDictionary(i, &enum_value_dictionary)); | |
| 192 CHECK(enum_value_dictionary->GetString("name", &enum_value)); | |
| 193 } | |
| 194 enum_values_.insert(std::move(enum_value)); | |
| 195 } | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 // Check if we should preserve null in objects. Right now, this is only used | |
| 200 // on arguments of type object and any (in fact, it's only used in the storage | |
| 201 // API), but it could potentially make sense for lists or functions as well. | |
| 202 if (type_ == ArgumentType::OBJECT || type_ == ArgumentType::ANY) | |
| 203 dict->GetBoolean("preserveNull", &preserve_null_); | |
| 204 } | |
| 205 | |
| 206 ArgumentSpec::~ArgumentSpec() {} | |
| 207 | |
| 208 bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context, | |
| 209 v8::Local<v8::Value> value, | |
| 210 const APITypeReferenceMap& refs, | |
| 211 std::unique_ptr<base::Value>* out_value, | |
| 212 std::string* error) const { | |
| 213 if (type_ == ArgumentType::FUNCTION) { | |
| 214 if (!value->IsFunction()) { | |
| 215 *error = GetInvalidTypeError(value); | |
| 216 return false; | |
| 217 } | |
| 218 | |
| 219 if (out_value) { | |
| 220 // Certain APIs (contextMenus) have functions as parameters other than the | |
| 221 // callback (contextMenus uses it for an onclick listener). Our generated | |
| 222 // types have adapted to consider functions "objects" and serialize them | |
| 223 // as dictionaries. | |
| 224 // TODO(devlin): It'd be awfully nice to get rid of this eccentricity. | |
| 225 *out_value = base::MakeUnique<base::DictionaryValue>(); | |
| 226 } | |
| 227 return true; | |
| 228 } | |
| 229 | |
| 230 if (type_ == ArgumentType::REF) { | |
| 231 DCHECK(ref_); | |
| 232 const ArgumentSpec* reference = refs.GetSpec(ref_.value()); | |
| 233 DCHECK(reference) << ref_.value(); | |
| 234 return reference->ParseArgument(context, value, refs, out_value, error); | |
| 235 } | |
| 236 | |
| 237 if (type_ == ArgumentType::CHOICES) { | |
| 238 for (const auto& choice : choices_) { | |
| 239 if (choice->ParseArgument(context, value, refs, out_value, error)) | |
| 240 return true; | |
| 241 } | |
| 242 *error = api_errors::InvalidChoice(); | |
| 243 return false; | |
| 244 } | |
| 245 | |
| 246 if (IsFundamentalType()) | |
| 247 return ParseArgumentToFundamental(context, value, out_value, error); | |
| 248 if (type_ == ArgumentType::OBJECT) { | |
| 249 // Don't allow functions or arrays (even though they are technically | |
| 250 // objects). This is to make it easier to match otherwise-ambiguous | |
| 251 // signatures. For instance, if an API method has an optional object | |
| 252 // parameter and then an optional callback, we wouldn't necessarily be able | |
| 253 // to match the arguments if we allowed functions as objects. | |
| 254 if (!value->IsObject() || value->IsFunction() || value->IsArray()) { | |
| 255 *error = GetInvalidTypeError(value); | |
| 256 return false; | |
| 257 } | |
| 258 v8::Local<v8::Object> object = value.As<v8::Object>(); | |
| 259 return ParseArgumentToObject(context, object, refs, out_value, error); | |
| 260 } | |
| 261 if (type_ == ArgumentType::LIST) { | |
| 262 if (!value->IsArray()) { | |
| 263 *error = GetInvalidTypeError(value); | |
| 264 return false; | |
| 265 } | |
| 266 v8::Local<v8::Array> array = value.As<v8::Array>(); | |
| 267 return ParseArgumentToArray(context, array, refs, out_value, error); | |
| 268 } | |
| 269 if (type_ == ArgumentType::BINARY) { | |
| 270 if (!value->IsArrayBuffer() && !value->IsArrayBufferView()) { | |
| 271 *error = GetInvalidTypeError(value); | |
| 272 return false; | |
| 273 } | |
| 274 return ParseArgumentToAny(context, value, out_value, error); | |
| 275 } | |
| 276 if (type_ == ArgumentType::ANY) | |
| 277 return ParseArgumentToAny(context, value, out_value, error); | |
| 278 NOTREACHED(); | |
| 279 return false; | |
| 280 } | |
| 281 | |
| 282 const std::string& ArgumentSpec::GetTypeName() const { | |
| 283 if (!type_name_.empty()) | |
| 284 return type_name_; | |
| 285 | |
| 286 switch (type_) { | |
| 287 case ArgumentType::INTEGER: | |
| 288 type_name_ = api_errors::kTypeInteger; | |
| 289 break; | |
| 290 case ArgumentType::DOUBLE: | |
| 291 type_name_ = api_errors::kTypeDouble; | |
| 292 break; | |
| 293 case ArgumentType::BOOLEAN: | |
| 294 type_name_ = api_errors::kTypeBoolean; | |
| 295 break; | |
| 296 case ArgumentType::STRING: | |
| 297 type_name_ = api_errors::kTypeString; | |
| 298 break; | |
| 299 case ArgumentType::OBJECT: | |
| 300 type_name_ = instance_of_ ? *instance_of_ : api_errors::kTypeObject; | |
| 301 break; | |
| 302 case ArgumentType::LIST: | |
| 303 type_name_ = api_errors::kTypeList; | |
| 304 break; | |
| 305 case ArgumentType::BINARY: | |
| 306 type_name_ = api_errors::kTypeBinary; | |
| 307 break; | |
| 308 case ArgumentType::FUNCTION: | |
| 309 type_name_ = api_errors::kTypeFunction; | |
| 310 break; | |
| 311 case ArgumentType::REF: | |
| 312 type_name_ = ref_->c_str(); | |
| 313 break; | |
| 314 case ArgumentType::CHOICES: { | |
| 315 std::vector<base::StringPiece> choices_strings; | |
| 316 choices_strings.reserve(choices_.size()); | |
| 317 for (const auto& choice : choices_) | |
| 318 choices_strings.push_back(choice->GetTypeName()); | |
| 319 type_name_ = base::StringPrintf( | |
| 320 "[%s]", base::JoinString(choices_strings, "|").c_str()); | |
| 321 break; | |
| 322 } | |
| 323 case ArgumentType::ANY: | |
| 324 type_name_ = api_errors::kTypeAny; | |
| 325 break; | |
| 326 } | |
| 327 DCHECK(!type_name_.empty()); | |
| 328 return type_name_; | |
| 329 } | |
| 330 | |
| 331 bool ArgumentSpec::IsFundamentalType() const { | |
| 332 return type_ == ArgumentType::INTEGER || type_ == ArgumentType::DOUBLE || | |
| 333 type_ == ArgumentType::BOOLEAN || type_ == ArgumentType::STRING; | |
| 334 } | |
| 335 | |
| 336 bool ArgumentSpec::ParseArgumentToFundamental( | |
| 337 v8::Local<v8::Context> context, | |
| 338 v8::Local<v8::Value> value, | |
| 339 std::unique_ptr<base::Value>* out_value, | |
| 340 std::string* error) const { | |
| 341 DCHECK(IsFundamentalType()); | |
| 342 | |
| 343 switch (type_) { | |
| 344 case ArgumentType::INTEGER: { | |
| 345 if (!value->IsInt32()) { | |
| 346 *error = GetInvalidTypeError(value); | |
| 347 return false; | |
| 348 } | |
| 349 int int_val = value.As<v8::Int32>()->Value(); | |
| 350 if (!CheckFundamentalBounds(int_val, minimum_, maximum_, error)) | |
| 351 return false; | |
| 352 if (out_value) | |
| 353 *out_value = base::MakeUnique<base::Value>(int_val); | |
| 354 return true; | |
| 355 } | |
| 356 case ArgumentType::DOUBLE: { | |
| 357 if (!value->IsNumber()) { | |
| 358 *error = GetInvalidTypeError(value); | |
| 359 return false; | |
| 360 } | |
| 361 double double_val = value.As<v8::Number>()->Value(); | |
| 362 if (!CheckFundamentalBounds(double_val, minimum_, maximum_, error)) | |
| 363 return false; | |
| 364 if (out_value) | |
| 365 *out_value = base::MakeUnique<base::Value>(double_val); | |
| 366 return true; | |
| 367 } | |
| 368 case ArgumentType::STRING: { | |
| 369 if (!value->IsString()) { | |
| 370 *error = GetInvalidTypeError(value); | |
| 371 return false; | |
| 372 } | |
| 373 | |
| 374 v8::Local<v8::String> v8_string = value.As<v8::String>(); | |
| 375 size_t length = static_cast<size_t>(v8_string->Length()); | |
| 376 if (min_length_ && length < *min_length_) { | |
| 377 *error = api_errors::TooFewStringChars(*min_length_, length); | |
| 378 return false; | |
| 379 } | |
| 380 | |
| 381 if (max_length_ && length > *max_length_) { | |
| 382 *error = api_errors::TooManyStringChars(*max_length_, length); | |
| 383 return false; | |
| 384 } | |
| 385 | |
| 386 // If we don't need to match enum values and don't need to convert, we're | |
| 387 // done... | |
| 388 if (!out_value && enum_values_.empty()) | |
| 389 return true; | |
| 390 // ...Otherwise, we need to convert to a std::string. | |
| 391 std::string s; | |
| 392 // We already checked that this is a string, so this should never fail. | |
| 393 CHECK(gin::Converter<std::string>::FromV8(context->GetIsolate(), value, | |
| 394 &s)); | |
| 395 if (!enum_values_.empty() && enum_values_.count(s) == 0) { | |
| 396 *error = api_errors::InvalidEnumValue(enum_values_); | |
| 397 return false; | |
| 398 } | |
| 399 if (out_value) { | |
| 400 // TODO(devlin): If base::Value ever takes a std::string&&, we | |
| 401 // could use std::move to construct. | |
| 402 *out_value = base::MakeUnique<base::Value>(s); | |
| 403 } | |
| 404 return true; | |
| 405 } | |
| 406 case ArgumentType::BOOLEAN: { | |
| 407 if (!value->IsBoolean()) { | |
| 408 *error = GetInvalidTypeError(value); | |
| 409 return false; | |
| 410 } | |
| 411 if (out_value) { | |
| 412 *out_value = | |
| 413 base::MakeUnique<base::Value>(value.As<v8::Boolean>()->Value()); | |
| 414 } | |
| 415 return true; | |
| 416 } | |
| 417 default: | |
| 418 NOTREACHED(); | |
| 419 } | |
| 420 return false; | |
| 421 } | |
| 422 | |
| 423 bool ArgumentSpec::ParseArgumentToObject( | |
| 424 v8::Local<v8::Context> context, | |
| 425 v8::Local<v8::Object> object, | |
| 426 const APITypeReferenceMap& refs, | |
| 427 std::unique_ptr<base::Value>* out_value, | |
| 428 std::string* error) const { | |
| 429 DCHECK_EQ(ArgumentType::OBJECT, type_); | |
| 430 std::unique_ptr<base::DictionaryValue> result; | |
| 431 // Only construct the result if we have an |out_value| to populate. | |
| 432 if (out_value) | |
| 433 result = base::MakeUnique<base::DictionaryValue>(); | |
| 434 | |
| 435 v8::Local<v8::Array> own_property_names; | |
| 436 if (!object->GetOwnPropertyNames(context).ToLocal(&own_property_names)) { | |
| 437 *error = api_errors::ScriptThrewError(); | |
| 438 return false; | |
| 439 } | |
| 440 | |
| 441 // Track all properties we see from |properties_| to check if any are missing. | |
| 442 // Use ArgumentSpec* instead of std::string for comparison + copy efficiency. | |
| 443 std::set<const ArgumentSpec*> seen_properties; | |
| 444 uint32_t length = own_property_names->Length(); | |
| 445 std::string property_error; | |
| 446 for (uint32_t i = 0; i < length; ++i) { | |
| 447 v8::Local<v8::Value> key; | |
| 448 if (!own_property_names->Get(context, i).ToLocal(&key)) { | |
| 449 *error = api_errors::ScriptThrewError(); | |
| 450 return false; | |
| 451 } | |
| 452 // In JS, all keys are strings or numbers (or symbols, but those are | |
| 453 // excluded by GetOwnPropertyNames()). If you try to set anything else | |
| 454 // (e.g. an object), it is converted to a string. | |
| 455 DCHECK(key->IsString() || key->IsNumber()); | |
| 456 v8::String::Utf8Value utf8_key(key); | |
| 457 | |
| 458 ArgumentSpec* property_spec = nullptr; | |
| 459 auto iter = properties_.find(*utf8_key); | |
| 460 bool allow_unserializable = false; | |
| 461 if (iter != properties_.end()) { | |
| 462 property_spec = iter->second.get(); | |
| 463 seen_properties.insert(property_spec); | |
| 464 } else if (additional_properties_) { | |
| 465 property_spec = additional_properties_.get(); | |
| 466 // additionalProperties: {type: any} is often used to allow anything | |
| 467 // through, including things that would normally break serialization like | |
| 468 // functions, or even NaN. If the additional properties are of | |
| 469 // ArgumentType::ANY, allow anything, even if it doesn't serialize. | |
| 470 allow_unserializable = property_spec->type_ == ArgumentType::ANY; | |
| 471 } else { | |
| 472 *error = api_errors::UnexpectedProperty(*utf8_key); | |
| 473 return false; | |
| 474 } | |
| 475 | |
| 476 v8::Local<v8::Value> prop_value; | |
| 477 // Fun: It's possible that a previous getter has removed the property from | |
| 478 // the object. This isn't that big of a deal, since it would only manifest | |
| 479 // in the case of some reasonably-crazy script objects, and it's probably | |
| 480 // not worth optimizing for the uncommon case to the detriment of the | |
| 481 // common (and either should be totally safe). We can always add a | |
| 482 // HasOwnProperty() check here in the future, if we desire. | |
| 483 // See also comment in ParseArgumentToArray() about passing in custom | |
| 484 // crazy values here. | |
| 485 if (!object->Get(context, key).ToLocal(&prop_value)) { | |
| 486 *error = api_errors::ScriptThrewError(); | |
| 487 return false; | |
| 488 } | |
| 489 | |
| 490 // Note: We don't serialize undefined, and only serialize null if it's part | |
| 491 // of the spec. | |
| 492 // TODO(devlin): This matches current behavior, but it is correct? And | |
| 493 // we treat undefined and null the same? | |
| 494 if (prop_value->IsUndefined() || prop_value->IsNull()) { | |
| 495 if (!property_spec->optional_) { | |
| 496 *error = api_errors::MissingRequiredProperty(*utf8_key); | |
| 497 return false; | |
| 498 } | |
| 499 if (preserve_null_ && prop_value->IsNull() && result) { | |
| 500 result->SetWithoutPathExpansion(*utf8_key, | |
| 501 base::MakeUnique<base::Value>()); | |
| 502 } | |
| 503 continue; | |
| 504 } | |
| 505 | |
| 506 std::unique_ptr<base::Value> property; | |
| 507 if (!property_spec->ParseArgument(context, prop_value, refs, | |
| 508 result ? &property : nullptr, | |
| 509 &property_error)) { | |
| 510 if (allow_unserializable) | |
| 511 continue; | |
| 512 *error = api_errors::PropertyError(*utf8_key, property_error); | |
| 513 return false; | |
| 514 } | |
| 515 if (result) | |
| 516 result->SetWithoutPathExpansion(*utf8_key, std::move(property)); | |
| 517 } | |
| 518 | |
| 519 for (const auto& pair : properties_) { | |
| 520 const ArgumentSpec* spec = pair.second.get(); | |
| 521 if (!spec->optional_ && seen_properties.count(spec) == 0) { | |
| 522 *error = api_errors::MissingRequiredProperty(pair.first.c_str()); | |
| 523 return false; | |
| 524 } | |
| 525 } | |
| 526 | |
| 527 if (instance_of_) { | |
| 528 // Check for the instance somewhere in the object's prototype chain. | |
| 529 // NOTE: This only checks that something in the prototype chain was | |
| 530 // constructed with the same name as the desired instance, but doesn't | |
| 531 // validate that it's the same constructor as the expected one. For | |
| 532 // instance, if we expect isInstanceOf == 'Date', script could pass in | |
| 533 // (function() { | |
| 534 // function Date() {} | |
| 535 // return new Date(); | |
| 536 // })() | |
| 537 // Since the object contains 'Date' in its prototype chain, this check | |
| 538 // succeeds, even though the object is not of built-in type Date. | |
| 539 // Since this isn't (or at least shouldn't be) a security check, this is | |
| 540 // okay. | |
| 541 bool found = false; | |
| 542 v8::Local<v8::Value> next_check = object; | |
| 543 do { | |
| 544 v8::Local<v8::Object> current = next_check.As<v8::Object>(); | |
| 545 v8::String::Utf8Value constructor(current->GetConstructorName()); | |
| 546 if (*instance_of_ == | |
| 547 base::StringPiece(*constructor, constructor.length())) { | |
| 548 found = true; | |
| 549 break; | |
| 550 } | |
| 551 next_check = current->GetPrototype(); | |
| 552 } while (next_check->IsObject()); | |
| 553 | |
| 554 if (!found) { | |
| 555 *error = api_errors::NotAnInstance(instance_of_->c_str()); | |
| 556 return false; | |
| 557 } | |
| 558 } | |
| 559 | |
| 560 if (out_value) | |
| 561 *out_value = std::move(result); | |
| 562 return true; | |
| 563 } | |
| 564 | |
| 565 bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context, | |
| 566 v8::Local<v8::Array> value, | |
| 567 const APITypeReferenceMap& refs, | |
| 568 std::unique_ptr<base::Value>* out_value, | |
| 569 std::string* error) const { | |
| 570 DCHECK_EQ(ArgumentType::LIST, type_); | |
| 571 | |
| 572 uint32_t length = value->Length(); | |
| 573 if (min_length_ && length < *min_length_) { | |
| 574 *error = api_errors::TooFewArrayItems(*min_length_, length); | |
| 575 return false; | |
| 576 } | |
| 577 | |
| 578 if (max_length_ && length > *max_length_) { | |
| 579 *error = api_errors::TooManyArrayItems(*max_length_, length); | |
| 580 return false; | |
| 581 } | |
| 582 | |
| 583 std::unique_ptr<base::ListValue> result; | |
| 584 // Only construct the result if we have an |out_value| to populate. | |
| 585 if (out_value) | |
| 586 result = base::MakeUnique<base::ListValue>(); | |
| 587 | |
| 588 std::string item_error; | |
| 589 for (uint32_t i = 0; i < length; ++i) { | |
| 590 v8::MaybeLocal<v8::Value> maybe_subvalue = value->Get(context, i); | |
| 591 v8::Local<v8::Value> subvalue; | |
| 592 // Note: This can fail in the case of a developer passing in the following: | |
| 593 // var a = []; | |
| 594 // Object.defineProperty(a, 0, { get: () => { throw new Error('foo'); } }); | |
| 595 // Currently, this will cause the developer-specified error ('foo') to be | |
| 596 // thrown. | |
| 597 // TODO(devlin): This is probably fine, but it's worth contemplating | |
| 598 // catching the error and throwing our own. | |
| 599 if (!maybe_subvalue.ToLocal(&subvalue)) | |
| 600 return false; | |
| 601 std::unique_ptr<base::Value> item; | |
| 602 if (!list_element_type_->ParseArgument( | |
| 603 context, subvalue, refs, result ? &item : nullptr, &item_error)) { | |
| 604 *error = api_errors::IndexError(i, item_error); | |
| 605 return false; | |
| 606 } | |
| 607 if (result) | |
| 608 result->Append(std::move(item)); | |
| 609 } | |
| 610 if (out_value) | |
| 611 *out_value = std::move(result); | |
| 612 return true; | |
| 613 } | |
| 614 | |
| 615 bool ArgumentSpec::ParseArgumentToAny(v8::Local<v8::Context> context, | |
| 616 v8::Local<v8::Value> value, | |
| 617 std::unique_ptr<base::Value>* out_value, | |
| 618 std::string* error) const { | |
| 619 DCHECK(type_ == ArgumentType::ANY || type_ == ArgumentType::BINARY); | |
| 620 if (out_value) { | |
| 621 std::unique_ptr<content::V8ValueConverter> converter = | |
| 622 content::V8ValueConverter::Create(); | |
| 623 converter->SetStripNullFromObjects(!preserve_null_); | |
| 624 std::unique_ptr<base::Value> converted = | |
| 625 converter->FromV8Value(value, context); | |
| 626 if (!converted) { | |
| 627 *error = api_errors::UnserializableValue(); | |
| 628 return false; | |
| 629 } | |
| 630 if (type_ == ArgumentType::BINARY) | |
| 631 DCHECK_EQ(base::Value::Type::BINARY, converted->GetType()); | |
| 632 *out_value = std::move(converted); | |
| 633 } | |
| 634 return true; | |
| 635 } | |
| 636 | |
| 637 std::string ArgumentSpec::GetInvalidTypeError( | |
| 638 v8::Local<v8::Value> value) const { | |
| 639 return api_errors::InvalidType(GetTypeName().c_str(), | |
| 640 GetV8ValueTypeString(value)); | |
| 641 } | |
| 642 | |
| 643 } // namespace extensions | |
| OLD | NEW |