Home | History | Annotate | Download | only in plugin
      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