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