Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(207)

Side by Side Diff: ppapi/native_client/src/trusted/plugin/json_manifest.cc

Issue 177113009: Support non-SFI mode in NaCl manifest file. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file. 4 * found in the LICENSE file.
5 */ 5 */
6 6
7 #include <algorithm> 7 #include <algorithm>
8 8
9 #include "ppapi/native_client/src/trusted/plugin/json_manifest.h" 9 #include "ppapi/native_client/src/trusted/plugin/json_manifest.h"
10 10
(...skipping 14 matching lines...) Expand all
25 namespace plugin { 25 namespace plugin {
26 26
27 namespace { 27 namespace {
28 // Top-level section name keys 28 // Top-level section name keys
29 const char* const kProgramKey = "program"; 29 const char* const kProgramKey = "program";
30 const char* const kInterpreterKey = "interpreter"; 30 const char* const kInterpreterKey = "interpreter";
31 const char* const kFilesKey = "files"; 31 const char* const kFilesKey = "files";
32 32
33 // ISA Dictionary keys 33 // ISA Dictionary keys
34 const char* const kX8632Key = "x86-32"; 34 const char* const kX8632Key = "x86-32";
35 const char* const kX8632NonSFIKey = "x86-32-nonsfi";
35 const char* const kX8664Key = "x86-64"; 36 const char* const kX8664Key = "x86-64";
37 const char* const kX8664NonSFIKey = "x86-64-nonsfi";
36 const char* const kArmKey = "arm"; 38 const char* const kArmKey = "arm";
39 const char* const kArmNonSFIKey = "arm-nonsfi";
37 const char* const kPortableKey = "portable"; 40 const char* const kPortableKey = "portable";
38 41
39 // Url Resolution keys 42 // Url Resolution keys
40 const char* const kPnaclTranslateKey = "pnacl-translate"; 43 const char* const kPnaclTranslateKey = "pnacl-translate";
41 const char* const kUrlKey = "url"; 44 const char* const kUrlKey = "url";
42 45
43 // PNaCl keys 46 // PNaCl keys
44 const char* const kOptLevelKey = "optlevel"; 47 const char* const kOptLevelKey = "optlevel";
45 48
46 // Sample NaCl manifest file: 49 // Sample NaCl manifest file:
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
82 // "files": { 85 // "files": {
83 // "foo.txt": { 86 // "foo.txt": {
84 // "portable": {"url": "foo.txt"} 87 // "portable": {"url": "foo.txt"}
85 // }, 88 // },
86 // "bar.txt": { 89 // "bar.txt": {
87 // "portable": {"url": "bar.txt"} 90 // "portable": {"url": "bar.txt"}
88 // } 91 // }
89 // } 92 // }
90 // } 93 // }
91 94
95 // Returns the key for the architecture in non-SFI mode.
96 std::string GetNonSFIKey(const std::string& sandbox_isa) {
97 return sandbox_isa + "-nonsfi";
98 }
99
92 // Looks up |property_name| in the vector |valid_names| with length 100 // Looks up |property_name| in the vector |valid_names| with length
93 // |valid_name_count|. Returns true if |property_name| is found. 101 // |valid_name_count|. Returns true if |property_name| is found.
94 bool FindMatchingProperty(const nacl::string& property_name, 102 bool FindMatchingProperty(const nacl::string& property_name,
95 const char** valid_names, 103 const char** valid_names,
96 size_t valid_name_count) { 104 size_t valid_name_count) {
97 for (size_t i = 0; i < valid_name_count; ++i) { 105 for (size_t i = 0; i < valid_name_count; ++i) {
98 if (property_name == valid_names[i]) { 106 if (property_name == valid_names[i]) {
99 return true; 107 return true;
100 } 108 }
101 } 109 }
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after
243 // is validated to have keys from within the set of recognized ISAs. Unknown 251 // is validated to have keys from within the set of recognized ISAs. Unknown
244 // ISAs are allowed, but ignored and warnings are produced. It is also validated 252 // ISAs are allowed, but ignored and warnings are produced. It is also validated
245 // that it must have an entry to match the ISA specified in |sandbox_isa| or 253 // that it must have an entry to match the ISA specified in |sandbox_isa| or
246 // have a fallback 'portable' entry if there is no match. Returns true if 254 // have a fallback 'portable' entry if there is no match. Returns true if
247 // |dictionary| is an ISA to URL map. Sets |error_info| to something 255 // |dictionary| is an ISA to URL map. Sets |error_info| to something
248 // descriptive if it fails. 256 // descriptive if it fails.
249 bool IsValidISADictionary(const Json::Value& dictionary, 257 bool IsValidISADictionary(const Json::Value& dictionary,
250 const nacl::string& parent_key, 258 const nacl::string& parent_key,
251 const nacl::string& sandbox_isa, 259 const nacl::string& sandbox_isa,
252 bool must_find_matching_entry, 260 bool must_find_matching_entry,
261 bool nonsfi_enabled,
253 ErrorInfo* error_info) { 262 ErrorInfo* error_info) {
254 if (error_info == NULL) return false; 263 if (error_info == NULL) return false;
255 264
256 // An ISA to URL dictionary has to be an object. 265 // An ISA to URL dictionary has to be an object.
257 if (!dictionary.isObject()) { 266 if (!dictionary.isObject()) {
258 error_info->SetReport(PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE, 267 error_info->SetReport(PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
259 nacl::string("manifest: ") + parent_key + 268 nacl::string("manifest: ") + parent_key +
260 " property is not an ISA to URL dictionary"); 269 " property is not an ISA to URL dictionary");
261 return false; 270 return false;
262 } 271 }
263 // Build the set of reserved ISA dictionary keys. 272 // Build the set of reserved ISA dictionary keys.
264 const char** isaProperties; 273 const char** isaProperties;
265 size_t isaPropertiesLength; 274 size_t isaPropertiesLength;
266 if (sandbox_isa == kPortableKey) { 275 if (sandbox_isa == kPortableKey) {
267 // The known values for PNaCl ISA dictionaries in the manifest. 276 // The known values for PNaCl ISA dictionaries in the manifest.
268 static const char* kPnaclManifestISAProperties[] = { 277 static const char* kPnaclManifestISAProperties[] = {
269 kPortableKey 278 kPortableKey
270 }; 279 };
271 isaProperties = kPnaclManifestISAProperties; 280 isaProperties = kPnaclManifestISAProperties;
272 isaPropertiesLength = NACL_ARRAY_SIZE(kPnaclManifestISAProperties); 281 isaPropertiesLength = NACL_ARRAY_SIZE(kPnaclManifestISAProperties);
273 } else { 282 } else {
274 // The known values for NaCl ISA dictionaries in the manifest. 283 // The known values for NaCl ISA dictionaries in the manifest.
275 static const char* kNaClManifestISAProperties[] = { 284 static const char* kNaClManifestISAProperties[] = {
276 kX8632Key, 285 kX8632Key,
286 kX8632NonSFIKey,
277 kX8664Key, 287 kX8664Key,
288 kX8664NonSFIKey,
278 kArmKey, 289 kArmKey,
290 kArmNonSFIKey,
279 // "portable" is here to allow checking that, if present, it can 291 // "portable" is here to allow checking that, if present, it can
280 // only refer to an URL, such as for a data file, and not to 292 // only refer to an URL, such as for a data file, and not to
281 // "pnacl-translate", which would cause the creation of a nexe. 293 // "pnacl-translate", which would cause the creation of a nexe.
282 kPortableKey 294 kPortableKey
283 }; 295 };
284 isaProperties = kNaClManifestISAProperties; 296 isaProperties = kNaClManifestISAProperties;
285 isaPropertiesLength = NACL_ARRAY_SIZE(kNaClManifestISAProperties); 297 isaPropertiesLength = NACL_ARRAY_SIZE(kNaClManifestISAProperties);
286 } 298 }
287 // Check that entries in the dictionary are structurally correct. 299 // Check that entries in the dictionary are structurally correct.
288 Json::Value::Members members = dictionary.getMemberNames(); 300 Json::Value::Members members = dictionary.getMemberNames();
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
336 error_info->SetReport( 348 error_info->SetReport(
337 PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH, 349 PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH,
338 nacl::string("manifest: no version of ") + parent_key + 350 nacl::string("manifest: no version of ") + parent_key +
339 " given for portable."); 351 " given for portable.");
340 return false; 352 return false;
341 } 353 }
342 } else if (must_find_matching_entry) { 354 } else if (must_find_matching_entry) {
343 // TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include 355 // TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include
344 // micro-architectures that can resolve to multiple valid sandboxes. 356 // micro-architectures that can resolve to multiple valid sandboxes.
345 bool has_isa = dictionary.isMember(sandbox_isa); 357 bool has_isa = dictionary.isMember(sandbox_isa);
358 bool has_nonsfi_isa =
359 nonsfi_enabled && dictionary.isMember(GetNonSFIKey(sandbox_isa));
346 bool has_portable = dictionary.isMember(kPortableKey); 360 bool has_portable = dictionary.isMember(kPortableKey);
347 361
348 if (!has_isa && !has_portable) { 362 if (!has_isa && !has_nonsfi_isa && !has_portable) {
349 error_info->SetReport( 363 error_info->SetReport(
350 PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH, 364 PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH,
351 nacl::string("manifest: no version of ") + parent_key + 365 nacl::string("manifest: no version of ") + parent_key +
352 " given for current arch and no portable version found."); 366 " given for current arch and no portable version found.");
353 return false; 367 return false;
354 } 368 }
355 } 369 }
356 370
357 return true; 371 return true;
358 } 372 }
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
421 return false; 435 return false;
422 } 436 }
423 437
424 // Validate the program section. 438 // Validate the program section.
425 // There must be a matching (portable or sandbox_isa_) entry for program for 439 // There must be a matching (portable or sandbox_isa_) entry for program for
426 // NaCl. 440 // NaCl.
427 if (!IsValidISADictionary(dictionary_[kProgramKey], 441 if (!IsValidISADictionary(dictionary_[kProgramKey],
428 kProgramKey, 442 kProgramKey,
429 sandbox_isa_, 443 sandbox_isa_,
430 true, 444 true,
445 nonsfi_enabled_,
431 error_info)) { 446 error_info)) {
432 return false; 447 return false;
433 } 448 }
434 449
435 // Validate the interpreter section (if given). 450 // Validate the interpreter section (if given).
436 // There must be a matching (portable or sandbox_isa_) entry for interpreter 451 // There must be a matching (portable or sandbox_isa_) entry for interpreter
437 // for NaCl. 452 // for NaCl.
438 if (dictionary_.isMember(kInterpreterKey)) { 453 if (dictionary_.isMember(kInterpreterKey)) {
439 if (!IsValidISADictionary(dictionary_[kInterpreterKey], 454 if (!IsValidISADictionary(dictionary_[kInterpreterKey],
440 kInterpreterKey, 455 kInterpreterKey,
441 sandbox_isa_, 456 sandbox_isa_,
442 true, 457 true,
458 nonsfi_enabled_,
443 error_info)) { 459 error_info)) {
444 return false; 460 return false;
445 } 461 }
446 } 462 }
447 463
448 // Validate the file dictionary (if given). 464 // Validate the file dictionary (if given).
449 // The "files" key does not require a matching (portable or sandbox_isa_) 465 // The "files" key does not require a matching (portable or sandbox_isa_)
450 // entry at schema validation time for NaCl. This allows manifests to specify 466 // entry at schema validation time for NaCl. This allows manifests to specify
451 // resources that are only loaded for a particular sandbox_isa. 467 // resources that are only loaded for a particular sandbox_isa.
452 if (dictionary_.isMember(kFilesKey)) { 468 if (dictionary_.isMember(kFilesKey)) {
453 const Json::Value& files = dictionary_[kFilesKey]; 469 const Json::Value& files = dictionary_[kFilesKey];
454 if (!files.isObject()) { 470 if (!files.isObject()) {
455 error_info->SetReport( 471 error_info->SetReport(
456 PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE, 472 PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
457 nacl::string("manifest: '") + kFilesKey + "' is not a dictionary."); 473 nacl::string("manifest: '") + kFilesKey + "' is not a dictionary.");
458 } 474 }
459 Json::Value::Members members = files.getMemberNames(); 475 Json::Value::Members members = files.getMemberNames();
460 for (size_t i = 0; i < members.size(); ++i) { 476 for (size_t i = 0; i < members.size(); ++i) {
461 nacl::string file_name = members[i]; 477 nacl::string file_name = members[i];
462 if (!IsValidISADictionary(files[file_name], 478 if (!IsValidISADictionary(files[file_name],
463 file_name, 479 file_name,
464 sandbox_isa_, 480 sandbox_isa_,
465 false, 481 false,
482 nonsfi_enabled_,
466 error_info)) { 483 error_info)) {
467 return false; 484 return false;
468 } 485 }
469 } 486 }
470 } 487 }
471 488
472 return true; 489 return true;
473 } 490 }
474 491
475 bool JsonManifest::GetURLFromISADictionary(const Json::Value& dictionary, 492 bool JsonManifest::GetURLFromISADictionary(const Json::Value& dictionary,
476 const nacl::string& parent_key, 493 const nacl::string& parent_key,
477 nacl::string* url, 494 nacl::string* url,
478 PnaclOptions* pnacl_options, 495 PnaclOptions* pnacl_options,
496 bool* nonsfi_enabled,
Mark Seaborn 2014/02/27 17:10:22 This is very close to "nonsfi_enabled_", so it wou
hidehiko 2014/02/28 06:41:54 Done.
479 ErrorInfo* error_info) const { 497 ErrorInfo* error_info) const {
480 DCHECK(url != NULL && pnacl_options != NULL && error_info != NULL); 498 DCHECK(url != NULL && pnacl_options != NULL && error_info != NULL);
481 499
482 // When the application actually requests a resolved URL, we must have 500 // When the application actually requests a resolved URL, we must have
483 // a matching entry (sandbox_isa_ or portable) for NaCl. 501 // a matching entry (sandbox_isa_ or portable) for NaCl.
484 if (!IsValidISADictionary(dictionary, parent_key, sandbox_isa_, true, 502 if (!IsValidISADictionary(dictionary, parent_key, sandbox_isa_, true,
485 error_info)) { 503 nonsfi_enabled_, error_info)) {
486 error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL, 504 error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
487 "architecture " + sandbox_isa_ + 505 "architecture " + sandbox_isa_ +
488 " is not found for file " + parent_key); 506 " is not found for file " + parent_key);
489 return false; 507 return false;
490 } 508 }
491 509
492 *url = ""; 510 *url = "";
493 511
494 // The call to IsValidISADictionary() above guarantees that either 512 // The call to IsValidISADictionary() above guarantees that either
495 // sandbox_isa_ or kPortableKey is present in the dictionary. 513 // sandbox_isa_ or kPortableKey is present in the dictionary.
496 bool has_portable = dictionary.isMember(kPortableKey); 514 bool has_portable = dictionary.isMember(kPortableKey);
497 bool has_isa = dictionary.isMember(sandbox_isa_); 515 bool has_isa = dictionary.isMember(sandbox_isa_);
516 // True iff the non-SFI mode is enabled, and the nmf as the -nonsfi entry.
517 bool has_nonsfi_isa =
518 nonsfi_enabled_ && dictionary.isMember(GetNonSFIKey(sandbox_isa_));
498 nacl::string chosen_isa; 519 nacl::string chosen_isa;
499 if ((sandbox_isa_ == kPortableKey) || (has_portable && !has_isa)) { 520 if ((sandbox_isa_ == kPortableKey) ||
521 (has_portable && !has_isa && !has_nonsfi_isa)) {
500 chosen_isa = kPortableKey; 522 chosen_isa = kPortableKey;
501 } else { 523 } else {
502 chosen_isa = sandbox_isa_; 524 // Choose non-SFI iff available.
525 if (has_nonsfi_isa) {
526 chosen_isa = GetNonSFIKey(sandbox_isa_);
527 } else {
528 chosen_isa = sandbox_isa_;
529 }
503 } 530 }
504 const Json::Value& isa_spec = dictionary[chosen_isa]; 531 const Json::Value& isa_spec = dictionary[chosen_isa];
505 // Check if this requires a pnacl-translate, otherwise just grab the URL. 532 // Check if this requires a pnacl-translate, otherwise just grab the URL.
506 // We may have pnacl-translate for isa-specific bitcode for CPU tuning. 533 // We may have pnacl-translate for isa-specific bitcode for CPU tuning.
507 if (isa_spec.isMember(kPnaclTranslateKey)) { 534 if (isa_spec.isMember(kPnaclTranslateKey)) {
508 // PNaCl 535 // PNaCl
509 GrabUrlAndPnaclOptions(isa_spec[kPnaclTranslateKey], url, pnacl_options); 536 GrabUrlAndPnaclOptions(isa_spec[kPnaclTranslateKey], url, pnacl_options);
510 pnacl_options->set_translate(true); 537 pnacl_options->set_translate(true);
538 if (nonsfi_enabled)
Mark Seaborn 2014/02/27 17:10:22 It might be a little simpler to require the caller
hidehiko 2014/02/28 06:41:54 Done.
539 *nonsfi_enabled = false;
511 } else { 540 } else {
512 // NaCl 541 // NaCl
513 *url = isa_spec[kUrlKey].asString(); 542 *url = isa_spec[kUrlKey].asString();
514 pnacl_options->set_translate(false); 543 pnacl_options->set_translate(false);
544 if (nonsfi_enabled)
545 *nonsfi_enabled = has_nonsfi_isa;
515 } 546 }
516 547
517 return true; 548 return true;
518 } 549 }
519 550
520 bool JsonManifest::GetKeyUrl(const Json::Value& dictionary, 551 bool JsonManifest::GetKeyUrl(const Json::Value& dictionary,
521 const nacl::string& key, 552 const nacl::string& key,
522 nacl::string* full_url, 553 nacl::string* full_url,
523 PnaclOptions* pnacl_options, 554 PnaclOptions* pnacl_options,
524 ErrorInfo* error_info) const { 555 ErrorInfo* error_info) const {
525 DCHECK(full_url != NULL && pnacl_options != NULL && error_info != NULL); 556 DCHECK(full_url != NULL && pnacl_options != NULL && error_info != NULL);
526 if (!dictionary.isMember(key)) { 557 if (!dictionary.isMember(key)) {
527 error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL, 558 error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
528 "file key not found in manifest"); 559 "file key not found in manifest");
529 return false; 560 return false;
530 } 561 }
531 const Json::Value& isa_dict = dictionary[key]; 562 const Json::Value& isa_dict = dictionary[key];
532 nacl::string relative_url; 563 nacl::string relative_url;
533 if (!GetURLFromISADictionary(isa_dict, key, &relative_url, 564 if (!GetURLFromISADictionary(isa_dict, key, &relative_url,
534 pnacl_options, error_info)) { 565 pnacl_options, NULL, error_info)) {
535 return false; 566 return false;
536 } 567 }
537 return ResolveURL(relative_url, full_url, error_info); 568 return ResolveURL(relative_url, full_url, error_info);
538 } 569 }
539 570
540 bool JsonManifest::ResolveURL(const nacl::string& relative_url, 571 bool JsonManifest::ResolveURL(const nacl::string& relative_url,
541 nacl::string* full_url, 572 nacl::string* full_url,
542 ErrorInfo* error_info) const { 573 ErrorInfo* error_info) const {
543 // The contents of the manifest are resolved relative to the manifest URL. 574 // The contents of the manifest are resolved relative to the manifest URL.
544 CHECK(url_util_ != NULL); 575 CHECK(url_util_ != NULL);
545 pp::Var resolved_url = 576 pp::Var resolved_url =
546 url_util_->ResolveRelativeToURL(pp::Var(manifest_base_url_), 577 url_util_->ResolveRelativeToURL(pp::Var(manifest_base_url_),
547 relative_url); 578 relative_url);
548 if (!resolved_url.is_string()) { 579 if (!resolved_url.is_string()) {
549 error_info->SetReport( 580 error_info->SetReport(
550 PP_NACL_ERROR_MANIFEST_RESOLVE_URL, 581 PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
551 "could not resolve url '" + relative_url + 582 "could not resolve url '" + relative_url +
552 "' relative to manifest base url '" + manifest_base_url_.c_str() + 583 "' relative to manifest base url '" + manifest_base_url_.c_str() +
553 "'."); 584 "'.");
554 return false; 585 return false;
555 } 586 }
556 *full_url = resolved_url.AsString(); 587 *full_url = resolved_url.AsString();
557 return true; 588 return true;
558 } 589 }
559 590
560 bool JsonManifest::GetProgramURL(nacl::string* full_url, 591 bool JsonManifest::GetProgramURL(nacl::string* full_url,
561 PnaclOptions* pnacl_options, 592 PnaclOptions* pnacl_options,
593 bool* nonsfi_enabled,
562 ErrorInfo* error_info) const { 594 ErrorInfo* error_info) const {
563 if (full_url == NULL || pnacl_options == NULL || error_info == NULL) 595 if (full_url == NULL || pnacl_options == NULL || error_info == NULL)
564 return false; 596 return false;
565 597
566 Json::Value program = dictionary_[kProgramKey]; 598 const Json::Value& program = dictionary_[kProgramKey];
567 599
568 nacl::string nexe_url; 600 nacl::string nexe_url;
569 nacl::string error_string; 601 nacl::string error_string;
570 602
571 if (!GetURLFromISADictionary(program, 603 if (!GetURLFromISADictionary(program,
572 kProgramKey, 604 kProgramKey,
573 &nexe_url, 605 &nexe_url,
574 pnacl_options, 606 pnacl_options,
607 nonsfi_enabled,
575 error_info)) { 608 error_info)) {
576 return false; 609 return false;
577 } 610 }
578 611
579 return ResolveURL(nexe_url, full_url, error_info); 612 return ResolveURL(nexe_url, full_url, error_info);
580 } 613 }
581 614
582 bool JsonManifest::GetFileKeys(std::set<nacl::string>* keys) const { 615 bool JsonManifest::GetFileKeys(std::set<nacl::string>* keys) const {
583 if (!dictionary_.isMember(kFilesKey)) { 616 if (!dictionary_.isMember(kFilesKey)) {
584 // trivial success: no keys when there is no "files" section. 617 // trivial success: no keys when there is no "files" section.
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
635 if (!files.isMember(rest)) { 668 if (!files.isMember(rest)) {
636 error_info->SetReport( 669 error_info->SetReport(
637 PP_NACL_ERROR_MANIFEST_RESOLVE_URL, 670 PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
638 nacl::string("ResolveKey: no such \"files\" entry: ") + key); 671 nacl::string("ResolveKey: no such \"files\" entry: ") + key);
639 return false; 672 return false;
640 } 673 }
641 return GetKeyUrl(files, rest, full_url, pnacl_options, error_info); 674 return GetKeyUrl(files, rest, full_url, pnacl_options, error_info);
642 } 675 }
643 676
644 } // namespace plugin 677 } // namespace plugin
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698