1 /* 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. 3 * Use of this source code is governed by a BSD-style license that can be 4 * found in the LICENSE file. 5 */ 6 7 #include <algorithm> 8 9 #include "ppapi/native_client/src/trusted/plugin/json_manifest.h" 10 11 #include <stdlib.h> 12 13 #include "native_client/src/include/nacl_base.h" 14 #include "native_client/src/include/nacl_macros.h" 15 #include "native_client/src/include/nacl_string.h" 16 #include "native_client/src/include/portability.h" 17 #include "native_client/src/shared/platform/nacl_check.h" 18 #include "ppapi/cpp/dev/url_util_dev.h" 19 #include "ppapi/cpp/var.h" 20 #include "ppapi/native_client/src/trusted/plugin/plugin_error.h" 21 #include "ppapi/native_client/src/trusted/plugin/pnacl_options.h" 22 #include "ppapi/native_client/src/trusted/plugin/utility.h" 23 #include "third_party/jsoncpp/source/include/json/reader.h" 24 25 namespace plugin { 26 27 namespace { 28 // Top-level section name keys 29 const char* const kProgramKey = "program"; 30 const char* const kInterpreterKey = "interpreter"; 31 const char* const kFilesKey = "files"; 32 33 // ISA Dictionary keys 34 const char* const kX8632Key = "x86-32"; 35 const char* const kX8664Key = "x86-64"; 36 const char* const kArmKey = "arm"; 37 const char* const kPortableKey = "portable"; 38 39 // Url Resolution keys 40 const char* const kPnaclTranslateKey = "pnacl-translate"; 41 const char* const kUrlKey = "url"; 42 43 // PNaCl keys 44 const char* const kOptLevelKey = "optlevel"; 45 // DEPRECATED! TODO(jvoung): remove the error message after launch. 46 const char* const kOptLevelKeyDeprecated = "-O"; 47 48 // Sample NaCl manifest file: 49 // { 50 // "program": { 51 // "x86-32": {"url": "myprogram_x86-32.nexe"}, 52 // "x86-64": {"url": "myprogram_x86-64.nexe"}, 53 // "arm": {"url": "myprogram_arm.nexe"} 54 // }, 55 // "interpreter": { 56 // "x86-32": {"url": "interpreter_x86-32.nexe"}, 57 // "x86-64": {"url": "interpreter_x86-64.nexe"}, 58 // "arm": {"url": "interpreter_arm.nexe"} 59 // }, 60 // "files": { 61 // "foo.txt": { 62 // "portable": {"url": "foo.txt"} 63 // }, 64 // "bar.txt": { 65 // "x86-32": {"url": "x86-32/bar.txt"}, 66 // "portable": {"url": "bar.txt"} 67 // }, 68 // "libfoo.so": { 69 // "x86-64" : { "url": "..." } 70 // } 71 // } 72 // } 73 74 // Sample PNaCl manifest file: 75 // { 76 // "program": { 77 // "portable": { 78 // "pnacl-translate": { 79 // "url": "myprogram.pexe", 80 // "optlevel": 0 81 // } 82 // } 83 // }, 84 // "files": { 85 // "foo.txt": { 86 // "portable": {"url": "foo.txt"} 87 // }, 88 // "bar.txt": { 89 // "portable": {"url": "bar.txt"} 90 // } 91 // } 92 // } 93 94 // Looks up |property_name| in the vector |valid_names| with length 95 // |valid_name_count|. Returns true if |property_name| is found. 96 bool FindMatchingProperty(const nacl::string& property_name, 97 const char** valid_names, 98 size_t valid_name_count) { 99 for (size_t i = 0; i < valid_name_count; ++i) { 100 if (property_name == valid_names[i]) { 101 return true; 102 } 103 } 104 return false; 105 } 106 107 // Return true if this is a valid dictionary. Having only keys present in 108 // |valid_keys| and having at least the keys in |required_keys|. 109 // Error messages will be placed in |error_string|, given that the dictionary 110 // was the property value of |container_key|. 111 // E.g., "container_key" : dictionary 112 bool IsValidDictionary(const Json::Value& dictionary, 113 const nacl::string& container_key, 114 const nacl::string& parent_key, 115 const char** valid_keys, 116 size_t valid_key_count, 117 const char** required_keys, 118 size_t required_key_count, 119 nacl::string* error_string) { 120 if (!dictionary.isObject()) { 121 nacl::stringstream error_stream; 122 error_stream << parent_key << " property '" << container_key 123 << "' is non-dictionary value '" 124 << dictionary.toStyledString() << "'."; 125 *error_string = error_stream.str(); 126 return false; 127 } 128 // Check for unknown dictionary members. 129 Json::Value::Members members = dictionary.getMemberNames(); 130 for (size_t i = 0; i < members.size(); ++i) { 131 nacl::string property_name = members[i]; 132 if (!FindMatchingProperty(property_name, 133 valid_keys, 134 valid_key_count)) { 135 // For forward compatibility, we do not prohibit other keys being in 136 // the dictionary. 137 PLUGIN_PRINTF(("WARNING: '%s' property '%s' has unknown key '%s'.\n", 138 parent_key.c_str(), 139 container_key.c_str(), property_name.c_str())); 140 } 141 } 142 // Check for required members. 143 for (size_t i = 0; i < required_key_count; ++i) { 144 if (!dictionary.isMember(required_keys[i])) { 145 nacl::stringstream error_stream; 146 error_stream << parent_key << " property '" << container_key 147 << "' does not have required key: '" 148 << required_keys[i] << "'."; 149 *error_string = error_stream.str(); 150 return false; 151 } 152 } 153 return true; 154 } 155 156 // Validate a "url" dictionary assuming it was resolved from container_key. 157 // E.g., "container_key" : { "url": "foo.txt" } 158 bool IsValidUrlSpec(const Json::Value& url_spec, 159 const nacl::string& container_key, 160 const nacl::string& parent_key, 161 const nacl::string& sandbox_isa, 162 nacl::string* error_string) { 163 static const char* kManifestUrlSpecRequired[] = { 164 kUrlKey 165 }; 166 const char** urlSpecPlusOptional; 167 size_t urlSpecPlusOptionalLength; 168 if (sandbox_isa == kPortableKey) { 169 static const char* kPnaclUrlSpecPlusOptional[] = { 170 kUrlKey, 171 kOptLevelKey, 172 }; 173 urlSpecPlusOptional = kPnaclUrlSpecPlusOptional; 174 urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kPnaclUrlSpecPlusOptional); 175 } else { 176 urlSpecPlusOptional = kManifestUrlSpecRequired; 177 urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kManifestUrlSpecRequired); 178 } 179 if (!IsValidDictionary(url_spec, container_key, parent_key, 180 urlSpecPlusOptional, 181 urlSpecPlusOptionalLength, 182 kManifestUrlSpecRequired, 183 NACL_ARRAY_SIZE(kManifestUrlSpecRequired), 184 error_string)) { 185 return false; 186 } 187 // URL specifications must not contain "pnacl-translate" keys. 188 // This prohibits NaCl clients from invoking PNaCl. 189 Json::Value translate = url_spec[kPnaclTranslateKey]; 190 if (!translate.empty()) { 191 nacl::stringstream error_stream; 192 error_stream << parent_key << " property '" << container_key << 193 "' has '" << kPnaclTranslateKey << "' inside URL spec."; 194 *error_string = error_stream.str(); 195 return false; 196 } 197 // Verify the correct types of the fields if they exist. 198 Json::Value url = url_spec[kUrlKey]; 199 if (!url.isString()) { 200 nacl::stringstream error_stream; 201 error_stream << parent_key << " property '" << container_key << 202 "' has non-string value '" << url.toStyledString() << 203 "' for key '" << kUrlKey << "'."; 204 *error_string = error_stream.str(); 205 return false; 206 } 207 Json::Value opt_level = url_spec[kOptLevelKey]; 208 if (!opt_level.empty() && !opt_level.isNumeric()) { 209 nacl::stringstream error_stream; 210 error_stream << parent_key << " property '" << container_key << 211 "' has non-numeric value '" << opt_level.toStyledString() << 212 "' for key '" << kOptLevelKey << "'."; 213 *error_string = error_stream.str(); 214 return false; 215 } 216 if (url_spec.isMember(kOptLevelKeyDeprecated)) { 217 nacl::stringstream error_stream; 218 error_stream << parent_key << " property '" << container_key << 219 "' has deprecated key '" << kOptLevelKeyDeprecated << 220 "' please use '" << kOptLevelKey << "' instead."; 221 *error_string = error_stream.str(); 222 return false; 223 } 224 return true; 225 } 226 227 // Validate a "pnacl-translate" dictionary, assuming it was resolved from 228 // container_key. E.g., "container_key" : { "pnacl_translate" : URLSpec } 229 bool IsValidPnaclTranslateSpec(const Json::Value& pnacl_spec, 230 const nacl::string& container_key, 231 const nacl::string& parent_key, 232 const nacl::string& sandbox_isa, 233 nacl::string* error_string) { 234 static const char* kManifestPnaclSpecProperties[] = { 235 kPnaclTranslateKey 236 }; 237 if (!IsValidDictionary(pnacl_spec, container_key, parent_key, 238 kManifestPnaclSpecProperties, 239 NACL_ARRAY_SIZE(kManifestPnaclSpecProperties), 240 kManifestPnaclSpecProperties, 241 NACL_ARRAY_SIZE(kManifestPnaclSpecProperties), 242 error_string)) { 243 return false; 244 } 245 Json::Value url_spec = pnacl_spec[kPnaclTranslateKey]; 246 if (!IsValidUrlSpec(url_spec, kPnaclTranslateKey, 247 container_key, sandbox_isa, error_string)) { 248 return false; 249 } 250 return true; 251 } 252 253 // Validates that |dictionary| is a valid ISA dictionary. An ISA dictionary 254 // is validated to have keys from within the set of recognized ISAs. Unknown 255 // ISAs are allowed, but ignored and warnings are produced. It is also validated 256 // that it must have an entry to match the ISA specified in |sandbox_isa| or 257 // have a fallback 'portable' entry if there is no match. Returns true if 258 // |dictionary| is an ISA to URL map. Sets |error_info| to something 259 // descriptive if it fails. 260 bool IsValidISADictionary(const Json::Value& dictionary, 261 const nacl::string& parent_key, 262 const nacl::string& sandbox_isa, 263 bool must_find_matching_entry, 264 ErrorInfo* error_info) { 265 if (error_info == NULL) return false; 266 267 // An ISA to URL dictionary has to be an object. 268 if (!dictionary.isObject()) { 269 error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE, 270 nacl::string("manifest: ") + parent_key + 271 " property is not an ISA to URL dictionary"); 272 return false; 273 } 274 // Build the set of reserved ISA dictionary keys. 275 const char** isaProperties; 276 size_t isaPropertiesLength; 277 if (sandbox_isa == kPortableKey) { 278 // The known values for PNaCl ISA dictionaries in the manifest. 279 static const char* kPnaclManifestISAProperties[] = { 280 kPortableKey 281 }; 282 isaProperties = kPnaclManifestISAProperties; 283 isaPropertiesLength = NACL_ARRAY_SIZE(kPnaclManifestISAProperties); 284 } else { 285 // The known values for NaCl ISA dictionaries in the manifest. 286 static const char* kNaClManifestISAProperties[] = { 287 kX8632Key, 288 kX8664Key, 289 kArmKey, 290 // "portable" is here to allow checking that, if present, it can 291 // only refer to an URL, such as for a data file, and not to 292 // "pnacl-translate", which would cause the creation of a nexe. 293 kPortableKey 294 }; 295 isaProperties = kNaClManifestISAProperties; 296 isaPropertiesLength = NACL_ARRAY_SIZE(kNaClManifestISAProperties); 297 } 298 // Check that entries in the dictionary are structurally correct. 299 Json::Value::Members members = dictionary.getMemberNames(); 300 for (size_t i = 0; i < members.size(); ++i) { 301 nacl::string property_name = members[i]; 302 Json::Value property_value = dictionary[property_name]; 303 nacl::string error_string; 304 if (FindMatchingProperty(property_name, 305 isaProperties, 306 isaPropertiesLength)) { 307 // For NaCl, arch entries can only be 308 // "arch/portable" : URLSpec 309 // For PNaCl arch in "program" dictionary entries can only be 310 // "portable" : { "pnacl-translate": URLSpec } 311 // For PNaCl arch elsewhere, dictionary entries can only be 312 // "portable" : URLSpec 313 if ((sandbox_isa != kPortableKey && 314 !IsValidUrlSpec(property_value, property_name, parent_key, 315 sandbox_isa, &error_string)) || 316 (sandbox_isa == kPortableKey && 317 parent_key == kProgramKey && 318 !IsValidPnaclTranslateSpec(property_value, property_name, parent_key, 319 sandbox_isa, &error_string)) || 320 (sandbox_isa == kPortableKey && 321 parent_key != kProgramKey && 322 !IsValidUrlSpec(property_value, property_name, parent_key, 323 sandbox_isa, &error_string))) { 324 error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE, 325 nacl::string("manifest: ") + error_string); 326 return false; 327 } 328 } else { 329 // For forward compatibility, we do not prohibit other keys being in 330 // the dictionary, as they may be architectures supported in later 331 // versions. However, the value of these entries must be an URLSpec. 332 PLUGIN_PRINTF(("IsValidISADictionary: unrecognized key '%s'.\n", 333 property_name.c_str())); 334 if (!IsValidUrlSpec(property_value, property_name, parent_key, 335 sandbox_isa, &error_string)) { 336 error_info->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE, 337 nacl::string("manifest: ") + error_string); 338 return false; 339 } 340 } 341 } 342 343 if (sandbox_isa == kPortableKey) { 344 bool has_portable = dictionary.isMember(kPortableKey); 345 346 if (!has_portable) { 347 error_info->SetReport( 348 ERROR_MANIFEST_PROGRAM_MISSING_ARCH, 349 nacl::string("manifest: no version of ") + parent_key + 350 " given for portable."); 351 return false; 352 } 353 } else if (must_find_matching_entry) { 354 // TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include 355 // micro-architectures that can resolve to multiple valid sandboxes. 356 bool has_isa = dictionary.isMember(sandbox_isa); 357 bool has_portable = dictionary.isMember(kPortableKey); 358 359 if (!has_isa && !has_portable) { 360 error_info->SetReport( 361 ERROR_MANIFEST_PROGRAM_MISSING_ARCH, 362 nacl::string("manifest: no version of ") + parent_key + 363 " given for current arch and no portable version found."); 364 return false; 365 } 366 } 367 368 return true; 369 } 370 371 void GrabUrlAndPnaclOptions(const Json::Value& url_spec, 372 nacl::string* url, 373 PnaclOptions* pnacl_options) { 374 *url = url_spec[kUrlKey].asString(); 375 if (url_spec.isMember(kOptLevelKey)) { 376 int32_t opt_raw = url_spec[kOptLevelKey].asInt(); 377 // set_opt_level will normalize the values. 378 pnacl_options->set_opt_level(opt_raw); 379 } 380 } 381 382 bool GetURLFromISADictionary(const Json::Value& dictionary, 383 const nacl::string& parent_key, 384 const nacl::string& sandbox_isa, 385 nacl::string* url, 386 PnaclOptions* pnacl_options, 387 ErrorInfo* error_info) { 388 if (url == NULL || pnacl_options == NULL || error_info == NULL) 389 return false; 390 391 // When the application actually requests a resolved URL, we must have 392 // a matching entry (sandbox_isa or portable) for NaCl. 393 if (!IsValidISADictionary(dictionary, parent_key, sandbox_isa, true, 394 error_info)) { 395 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, 396 "architecture " + sandbox_isa + 397 " is not found for file " + parent_key); 398 return false; 399 } 400 401 *url = ""; 402 403 // The call to IsValidISADictionary() above guarantees that either 404 // sandbox_isa or kPortableKey is present in the dictionary. 405 bool has_portable = dictionary.isMember(kPortableKey); 406 bool has_isa = dictionary.isMember(sandbox_isa); 407 nacl::string chosen_isa; 408 if ((sandbox_isa == kPortableKey) || (has_portable && !has_isa)) { 409 chosen_isa = kPortableKey; 410 } else { 411 chosen_isa = sandbox_isa; 412 } 413 const Json::Value& isa_spec = dictionary[chosen_isa]; 414 // Check if this requires a pnacl-translate, otherwise just grab the URL. 415 // We may have pnacl-translate for isa-specific bitcode for CPU tuning. 416 if (isa_spec.isMember(kPnaclTranslateKey)) { 417 // PNaCl 418 GrabUrlAndPnaclOptions(isa_spec[kPnaclTranslateKey], url, pnacl_options); 419 pnacl_options->set_translate(true); 420 } else { 421 // NaCl 422 *url = isa_spec[kUrlKey].asString(); 423 pnacl_options->set_translate(false); 424 } 425 426 return true; 427 } 428 429 bool GetKeyUrl(const Json::Value& dictionary, 430 const nacl::string& key, 431 const nacl::string& sandbox_isa, 432 const Manifest* manifest, 433 nacl::string* full_url, 434 PnaclOptions* pnacl_options, 435 ErrorInfo* error_info) { 436 CHECK(full_url != NULL && error_info != NULL); 437 if (!dictionary.isMember(key)) { 438 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, 439 "file key not found in manifest"); 440 return false; 441 } 442 const Json::Value& isa_dict = dictionary[key]; 443 nacl::string relative_url; 444 if (!GetURLFromISADictionary(isa_dict, key, sandbox_isa, &relative_url, 445 pnacl_options, error_info)) { 446 return false; 447 } 448 return manifest->ResolveURL(relative_url, full_url, error_info); 449 } 450 451 } // namespace 452 453 bool JsonManifest::Init(const nacl::string& manifest_json, 454 ErrorInfo* error_info) { 455 if (error_info == NULL) { 456 return false; 457 } 458 Json::Reader reader; 459 if (!reader.parse(manifest_json, dictionary_)) { 460 std::string json_error = reader.getFormatedErrorMessages(); 461 error_info->SetReport(ERROR_MANIFEST_PARSING, 462 "manifest JSON parsing failed: " + json_error); 463 return false; 464 } 465 // Parse has ensured the string was valid JSON. Check that it matches the 466 // manifest schema. 467 return MatchesSchema(error_info); 468 } 469 470 bool JsonManifest::MatchesSchema(ErrorInfo* error_info) { 471 pp::Var exception; 472 if (error_info == NULL) { 473 return false; 474 } 475 if (!dictionary_.isObject()) { 476 error_info->SetReport( 477 ERROR_MANIFEST_SCHEMA_VALIDATE, 478 "manifest: is not a json dictionary."); 479 return false; 480 } 481 Json::Value::Members members = dictionary_.getMemberNames(); 482 for (size_t i = 0; i < members.size(); ++i) { 483 // The top level dictionary entries valid in the manifest file. 484 static const char* kManifestTopLevelProperties[] = { kProgramKey, 485 kInterpreterKey, 486 kFilesKey }; 487 nacl::string property_name = members[i]; 488 if (!FindMatchingProperty(property_name, 489 kManifestTopLevelProperties, 490 NACL_ARRAY_SIZE(kManifestTopLevelProperties))) { 491 PLUGIN_PRINTF(("JsonManifest::MatchesSchema: WARNING: unknown top-level " 492 "section '%s' in manifest.\n", property_name.c_str())); 493 } 494 } 495 496 // A manifest file must have a program section. 497 if (!dictionary_.isMember(kProgramKey)) { 498 error_info->SetReport( 499 ERROR_MANIFEST_SCHEMA_VALIDATE, 500 nacl::string("manifest: missing '") + kProgramKey + "' section."); 501 return false; 502 } 503 504 // Validate the program section. 505 // There must be a matching (portable or sandbox_isa_) entry for program for 506 // NaCl. 507 if (!IsValidISADictionary(dictionary_[kProgramKey], 508 kProgramKey, 509 sandbox_isa_, 510 true, 511 error_info)) { 512 return false; 513 } 514 515 // Validate the interpreter section (if given). 516 // There must be a matching (portable or sandbox_isa_) entry for interpreter 517 // for NaCl. 518 if (dictionary_.isMember(kInterpreterKey)) { 519 if (!IsValidISADictionary(dictionary_[kInterpreterKey], 520 kInterpreterKey, 521 sandbox_isa_, 522 true, 523 error_info)) { 524 return false; 525 } 526 } 527 528 // Validate the file dictionary (if given). 529 // The "files" key does not require a matching (portable or sandbox_isa_) 530 // entry at schema validation time for NaCl. This allows manifests to specify 531 // resources that are only loaded for a particular sandbox_isa. 532 if (dictionary_.isMember(kFilesKey)) { 533 const Json::Value& files = dictionary_[kFilesKey]; 534 if (!files.isObject()) { 535 error_info->SetReport( 536 ERROR_MANIFEST_SCHEMA_VALIDATE, 537 nacl::string("manifest: '") + kFilesKey + "' is not a dictionary."); 538 } 539 Json::Value::Members members = files.getMemberNames(); 540 for (size_t i = 0; i < members.size(); ++i) { 541 nacl::string file_name = members[i]; 542 if (!IsValidISADictionary(files[file_name], 543 file_name, 544 sandbox_isa_, 545 false, 546 error_info)) { 547 return false; 548 } 549 } 550 } 551 552 return true; 553 } 554 555 bool JsonManifest::ResolveURL(const nacl::string& relative_url, 556 nacl::string* full_url, 557 ErrorInfo* error_info) const { 558 // The contents of the manifest are resolved relative to the manifest URL. 559 CHECK(url_util_ != NULL); 560 pp::Var resolved_url = 561 url_util_->ResolveRelativeToURL(pp::Var(manifest_base_url_), 562 relative_url); 563 if (!resolved_url.is_string()) { 564 error_info->SetReport( 565 ERROR_MANIFEST_RESOLVE_URL, 566 "could not resolve url '" + relative_url + 567 "' relative to manifest base url '" + manifest_base_url_.c_str() + 568 "'."); 569 return false; 570 } 571 *full_url = resolved_url.AsString(); 572 return true; 573 } 574 575 bool JsonManifest::GetProgramURL(nacl::string* full_url, 576 PnaclOptions* pnacl_options, 577 ErrorInfo* error_info) const { 578 if (full_url == NULL || pnacl_options == NULL || error_info == NULL) 579 return false; 580 581 Json::Value program = dictionary_[kProgramKey]; 582 583 nacl::string nexe_url; 584 nacl::string error_string; 585 586 if (!GetURLFromISADictionary(program, 587 kProgramKey, 588 sandbox_isa_, 589 &nexe_url, 590 pnacl_options, 591 error_info)) { 592 return false; 593 } 594 595 return ResolveURL(nexe_url, full_url, error_info); 596 } 597 598 bool JsonManifest::GetFileKeys(std::set<nacl::string>* keys) const { 599 if (!dictionary_.isMember(kFilesKey)) { 600 // trivial success: no keys when there is no "files" section. 601 return true; 602 } 603 const Json::Value& files = dictionary_[kFilesKey]; 604 CHECK(files.isObject()); 605 Json::Value::Members members = files.getMemberNames(); 606 for (size_t i = 0; i < members.size(); ++i) { 607 keys->insert(members[i]); 608 } 609 return true; 610 } 611 612 bool JsonManifest::ResolveKey(const nacl::string& key, 613 nacl::string* full_url, 614 PnaclOptions* pnacl_options, 615 ErrorInfo* error_info) const { 616 NaClLog(3, "JsonManifest::ResolveKey(%s)\n", key.c_str()); 617 // key must be one of kProgramKey or kFileKey '/' file-section-key 618 619 if (full_url == NULL || pnacl_options == NULL || error_info == NULL) 620 return false; 621 622 if (key == kProgramKey) { 623 return GetKeyUrl(dictionary_, key, sandbox_isa_, this, full_url, 624 pnacl_options, error_info); 625 } 626 nacl::string::const_iterator p = find(key.begin(), key.end(), '/'); 627 if (p == key.end()) { 628 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, 629 nacl::string("ResolveKey: invalid key, no slash: ") 630 + key); 631 return false; 632 } 633 634 // generalize to permit other sections? 635 nacl::string prefix(key.begin(), p); 636 if (prefix != kFilesKey) { 637 error_info->SetReport(ERROR_MANIFEST_RESOLVE_URL, 638 nacl::string("ResolveKey: invalid key: not \"files\"" 639 " prefix: ") + key); 640 return false; 641 } 642 643 nacl::string rest(p + 1, key.end()); 644 645 const Json::Value& files = dictionary_[kFilesKey]; 646 if (!files.isObject()) { 647 error_info->SetReport( 648 ERROR_MANIFEST_RESOLVE_URL, 649 nacl::string("ResolveKey: no \"files\" dictionary")); 650 return false; 651 } 652 if (!files.isMember(rest)) { 653 error_info->SetReport( 654 ERROR_MANIFEST_RESOLVE_URL, 655 nacl::string("ResolveKey: no such \"files\" entry: ") + key); 656 return false; 657 } 658 return GetKeyUrl(files, rest, sandbox_isa_, this, full_url, pnacl_options, 659 error_info); 660 } 661 662 } // namespace plugin 663