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