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