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.
9 #include "ppapi/native_client/src/trusted/plugin/json_manifest.h"
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"
28 // Top-level section name keys
29 const char* const kProgramKey
= "program";
30 const char* const kInterpreterKey
= "interpreter";
31 const char* const kFilesKey
= "files";
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";
39 // Url Resolution keys
40 const char* const kPnaclTranslateKey
= "pnacl-translate";
41 const char* const kUrlKey
= "url";
44 const char* const kOptLevelKey
= "optlevel";
45 // DEPRECATED! TODO(jvoung): remove the error message after launch.
46 const char* const kOptLevelKeyDeprecated
= "-O";
48 // Sample NaCl manifest file:
51 // "x86-32": {"url": "myprogram_x86-32.nexe"},
52 // "x86-64": {"url": "myprogram_x86-64.nexe"},
53 // "arm": {"url": "myprogram_arm.nexe"}
56 // "x86-32": {"url": "interpreter_x86-32.nexe"},
57 // "x86-64": {"url": "interpreter_x86-64.nexe"},
58 // "arm": {"url": "interpreter_arm.nexe"}
62 // "portable": {"url": "foo.txt"}
65 // "x86-32": {"url": "x86-32/bar.txt"},
66 // "portable": {"url": "bar.txt"}
69 // "x86-64" : { "url": "..." }
74 // Sample PNaCl manifest file:
78 // "pnacl-translate": {
79 // "url": "myprogram.pexe",
86 // "portable": {"url": "foo.txt"}
89 // "portable": {"url": "bar.txt"}
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
]) {
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();
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
,
135 // For forward compatibility, we do not prohibit other keys being in
137 PLUGIN_PRINTF(("WARNING: '%s' property '%s' has unknown key '%s'.\n",
139 container_key
.c_str(), property_name
.c_str()));
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();
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
[] = {
166 const char** urlSpecPlusOptional
;
167 size_t urlSpecPlusOptionalLength
;
168 if (sandbox_isa
== kPortableKey
) {
169 static const char* kPnaclUrlSpecPlusOptional
[] = {
173 urlSpecPlusOptional
= kPnaclUrlSpecPlusOptional
;
174 urlSpecPlusOptionalLength
= NACL_ARRAY_SIZE(kPnaclUrlSpecPlusOptional
);
176 // URL specifications must not contain "pnacl-translate" keys.
177 // This prohibits NaCl clients from invoking PNaCl.
178 if (url_spec
.isMember(kPnaclTranslateKey
)) {
179 nacl::stringstream error_stream
;
180 error_stream
<< "PNaCl-like NMF with application/x-nacl mimetype instead "
181 << "of x-pnacl mimetype (has " << kPnaclTranslateKey
<< ").";
182 *error_string
= error_stream
.str();
185 urlSpecPlusOptional
= kManifestUrlSpecRequired
;
186 urlSpecPlusOptionalLength
= NACL_ARRAY_SIZE(kManifestUrlSpecRequired
);
188 if (!IsValidDictionary(url_spec
, container_key
, parent_key
,
190 urlSpecPlusOptionalLength
,
191 kManifestUrlSpecRequired
,
192 NACL_ARRAY_SIZE(kManifestUrlSpecRequired
),
196 // Verify the correct types of the fields if they exist.
197 Json::Value url
= url_spec
[kUrlKey
];
198 if (!url
.isString()) {
199 nacl::stringstream error_stream
;
200 error_stream
<< parent_key
<< " property '" << container_key
<<
201 "' has non-string value '" << url
.toStyledString() <<
202 "' for key '" << kUrlKey
<< "'.";
203 *error_string
= error_stream
.str();
206 Json::Value opt_level
= url_spec
[kOptLevelKey
];
207 if (!opt_level
.empty() && !opt_level
.isNumeric()) {
208 nacl::stringstream error_stream
;
209 error_stream
<< parent_key
<< " property '" << container_key
<<
210 "' has non-numeric value '" << opt_level
.toStyledString() <<
211 "' for key '" << kOptLevelKey
<< "'.";
212 *error_string
= error_stream
.str();
215 if (url_spec
.isMember(kOptLevelKeyDeprecated
)) {
216 nacl::stringstream error_stream
;
217 error_stream
<< parent_key
<< " property '" << container_key
<<
218 "' has deprecated key '" << kOptLevelKeyDeprecated
<<
219 "' please use '" << kOptLevelKey
<< "' instead.";
220 *error_string
= error_stream
.str();
226 // Validate a "pnacl-translate" dictionary, assuming it was resolved from
227 // container_key. E.g., "container_key" : { "pnacl_translate" : URLSpec }
228 bool IsValidPnaclTranslateSpec(const Json::Value
& pnacl_spec
,
229 const nacl::string
& container_key
,
230 const nacl::string
& parent_key
,
231 const nacl::string
& sandbox_isa
,
232 nacl::string
* error_string
) {
233 static const char* kManifestPnaclSpecProperties
[] = {
236 if (!IsValidDictionary(pnacl_spec
, container_key
, parent_key
,
237 kManifestPnaclSpecProperties
,
238 NACL_ARRAY_SIZE(kManifestPnaclSpecProperties
),
239 kManifestPnaclSpecProperties
,
240 NACL_ARRAY_SIZE(kManifestPnaclSpecProperties
),
244 Json::Value url_spec
= pnacl_spec
[kPnaclTranslateKey
];
245 if (!IsValidUrlSpec(url_spec
, kPnaclTranslateKey
,
246 container_key
, sandbox_isa
, error_string
)) {
252 // Validates that |dictionary| is a valid ISA dictionary. An ISA dictionary
253 // is validated to have keys from within the set of recognized ISAs. Unknown
254 // ISAs are allowed, but ignored and warnings are produced. It is also validated
255 // that it must have an entry to match the ISA specified in |sandbox_isa| or
256 // have a fallback 'portable' entry if there is no match. Returns true if
257 // |dictionary| is an ISA to URL map. Sets |error_info| to something
258 // descriptive if it fails.
259 bool IsValidISADictionary(const Json::Value
& dictionary
,
260 const nacl::string
& parent_key
,
261 const nacl::string
& sandbox_isa
,
262 bool must_find_matching_entry
,
263 ErrorInfo
* error_info
) {
264 if (error_info
== NULL
) return false;
266 // An ISA to URL dictionary has to be an object.
267 if (!dictionary
.isObject()) {
268 error_info
->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE
,
269 nacl::string("manifest: ") + parent_key
+
270 " property is not an ISA to URL dictionary");
273 // Build the set of reserved ISA dictionary keys.
274 const char** isaProperties
;
275 size_t isaPropertiesLength
;
276 if (sandbox_isa
== kPortableKey
) {
277 // The known values for PNaCl ISA dictionaries in the manifest.
278 static const char* kPnaclManifestISAProperties
[] = {
281 isaProperties
= kPnaclManifestISAProperties
;
282 isaPropertiesLength
= NACL_ARRAY_SIZE(kPnaclManifestISAProperties
);
284 // The known values for NaCl ISA dictionaries in the manifest.
285 static const char* kNaClManifestISAProperties
[] = {
289 // "portable" is here to allow checking that, if present, it can
290 // only refer to an URL, such as for a data file, and not to
291 // "pnacl-translate", which would cause the creation of a nexe.
294 isaProperties
= kNaClManifestISAProperties
;
295 isaPropertiesLength
= NACL_ARRAY_SIZE(kNaClManifestISAProperties
);
297 // Check that entries in the dictionary are structurally correct.
298 Json::Value::Members members
= dictionary
.getMemberNames();
299 for (size_t i
= 0; i
< members
.size(); ++i
) {
300 nacl::string property_name
= members
[i
];
301 Json::Value property_value
= dictionary
[property_name
];
302 nacl::string error_string
;
303 if (FindMatchingProperty(property_name
,
305 isaPropertiesLength
)) {
306 // For NaCl, arch entries can only be
307 // "arch/portable" : URLSpec
308 // For PNaCl arch in "program" dictionary entries can only be
309 // "portable" : { "pnacl-translate": URLSpec }
310 // For PNaCl arch elsewhere, dictionary entries can only be
311 // "portable" : URLSpec
312 if ((sandbox_isa
!= kPortableKey
&&
313 !IsValidUrlSpec(property_value
, property_name
, parent_key
,
314 sandbox_isa
, &error_string
)) ||
315 (sandbox_isa
== kPortableKey
&&
316 parent_key
== kProgramKey
&&
317 !IsValidPnaclTranslateSpec(property_value
, property_name
, parent_key
,
318 sandbox_isa
, &error_string
)) ||
319 (sandbox_isa
== kPortableKey
&&
320 parent_key
!= kProgramKey
&&
321 !IsValidUrlSpec(property_value
, property_name
, parent_key
,
322 sandbox_isa
, &error_string
))) {
323 error_info
->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE
,
324 nacl::string("manifest: ") + error_string
);
328 // For forward compatibility, we do not prohibit other keys being in
329 // the dictionary, as they may be architectures supported in later
330 // versions. However, the value of these entries must be an URLSpec.
331 PLUGIN_PRINTF(("IsValidISADictionary: unrecognized key '%s'.\n",
332 property_name
.c_str()));
333 if (!IsValidUrlSpec(property_value
, property_name
, parent_key
,
334 sandbox_isa
, &error_string
)) {
335 error_info
->SetReport(ERROR_MANIFEST_SCHEMA_VALIDATE
,
336 nacl::string("manifest: ") + error_string
);
342 if (sandbox_isa
== kPortableKey
) {
343 bool has_portable
= dictionary
.isMember(kPortableKey
);
346 error_info
->SetReport(
347 ERROR_MANIFEST_PROGRAM_MISSING_ARCH
,
348 nacl::string("manifest: no version of ") + parent_key
+
349 " given for portable.");
352 } else if (must_find_matching_entry
) {
353 // TODO(elijahtaylor) add ISA resolver here if we expand ISAs to include
354 // micro-architectures that can resolve to multiple valid sandboxes.
355 bool has_isa
= dictionary
.isMember(sandbox_isa
);
356 bool has_portable
= dictionary
.isMember(kPortableKey
);
358 if (!has_isa
&& !has_portable
) {
359 error_info
->SetReport(
360 ERROR_MANIFEST_PROGRAM_MISSING_ARCH
,
361 nacl::string("manifest: no version of ") + parent_key
+
362 " given for current arch and no portable version found.");
370 void GrabUrlAndPnaclOptions(const Json::Value
& url_spec
,
372 PnaclOptions
* pnacl_options
) {
373 *url
= url_spec
[kUrlKey
].asString();
374 if (url_spec
.isMember(kOptLevelKey
)) {
375 int32_t opt_raw
= url_spec
[kOptLevelKey
].asInt();
376 // set_opt_level will normalize the values.
377 pnacl_options
->set_opt_level(opt_raw
);
381 bool GetURLFromISADictionary(const Json::Value
& dictionary
,
382 const nacl::string
& parent_key
,
383 const nacl::string
& sandbox_isa
,
385 PnaclOptions
* pnacl_options
,
386 ErrorInfo
* error_info
) {
387 if (url
== NULL
|| pnacl_options
== NULL
|| error_info
== NULL
)
390 // When the application actually requests a resolved URL, we must have
391 // a matching entry (sandbox_isa or portable) for NaCl.
392 if (!IsValidISADictionary(dictionary
, parent_key
, sandbox_isa
, true,
394 error_info
->SetReport(ERROR_MANIFEST_RESOLVE_URL
,
395 "architecture " + sandbox_isa
+
396 " is not found for file " + parent_key
);
402 // The call to IsValidISADictionary() above guarantees that either
403 // sandbox_isa or kPortableKey is present in the dictionary.
404 bool has_portable
= dictionary
.isMember(kPortableKey
);
405 bool has_isa
= dictionary
.isMember(sandbox_isa
);
406 nacl::string chosen_isa
;
407 if ((sandbox_isa
== kPortableKey
) || (has_portable
&& !has_isa
)) {
408 chosen_isa
= kPortableKey
;
410 chosen_isa
= sandbox_isa
;
412 const Json::Value
& isa_spec
= dictionary
[chosen_isa
];
413 // Check if this requires a pnacl-translate, otherwise just grab the URL.
414 // We may have pnacl-translate for isa-specific bitcode for CPU tuning.
415 if (isa_spec
.isMember(kPnaclTranslateKey
)) {
417 GrabUrlAndPnaclOptions(isa_spec
[kPnaclTranslateKey
], url
, pnacl_options
);
418 pnacl_options
->set_translate(true);
421 *url
= isa_spec
[kUrlKey
].asString();
422 pnacl_options
->set_translate(false);
428 bool GetKeyUrl(const Json::Value
& dictionary
,
429 const nacl::string
& key
,
430 const nacl::string
& sandbox_isa
,
431 const Manifest
* manifest
,
432 nacl::string
* full_url
,
433 PnaclOptions
* pnacl_options
,
434 ErrorInfo
* error_info
) {
435 CHECK(full_url
!= NULL
&& error_info
!= NULL
);
436 if (!dictionary
.isMember(key
)) {
437 error_info
->SetReport(ERROR_MANIFEST_RESOLVE_URL
,
438 "file key not found in manifest");
441 const Json::Value
& isa_dict
= dictionary
[key
];
442 nacl::string relative_url
;
443 if (!GetURLFromISADictionary(isa_dict
, key
, sandbox_isa
, &relative_url
,
444 pnacl_options
, error_info
)) {
447 return manifest
->ResolveURL(relative_url
, full_url
, error_info
);
452 bool JsonManifest::Init(const nacl::string
& manifest_json
,
453 ErrorInfo
* error_info
) {
454 if (error_info
== NULL
) {
458 if (!reader
.parse(manifest_json
, dictionary_
)) {
459 std::string json_error
= reader
.getFormatedErrorMessages();
460 error_info
->SetReport(ERROR_MANIFEST_PARSING
,
461 "manifest JSON parsing failed: " + json_error
);
464 // Parse has ensured the string was valid JSON. Check that it matches the
466 return MatchesSchema(error_info
);
469 bool JsonManifest::MatchesSchema(ErrorInfo
* error_info
) {
471 if (error_info
== NULL
) {
474 if (!dictionary_
.isObject()) {
475 error_info
->SetReport(
476 ERROR_MANIFEST_SCHEMA_VALIDATE
,
477 "manifest: is not a json dictionary.");
480 Json::Value::Members members
= dictionary_
.getMemberNames();
481 for (size_t i
= 0; i
< members
.size(); ++i
) {
482 // The top level dictionary entries valid in the manifest file.
483 static const char* kManifestTopLevelProperties
[] = { kProgramKey
,
486 nacl::string property_name
= members
[i
];
487 if (!FindMatchingProperty(property_name
,
488 kManifestTopLevelProperties
,
489 NACL_ARRAY_SIZE(kManifestTopLevelProperties
))) {
490 PLUGIN_PRINTF(("JsonManifest::MatchesSchema: WARNING: unknown top-level "
491 "section '%s' in manifest.\n", property_name
.c_str()));
495 // A manifest file must have a program section.
496 if (!dictionary_
.isMember(kProgramKey
)) {
497 error_info
->SetReport(
498 ERROR_MANIFEST_SCHEMA_VALIDATE
,
499 nacl::string("manifest: missing '") + kProgramKey
+ "' section.");
503 // Validate the program section.
504 // There must be a matching (portable or sandbox_isa_) entry for program for
506 if (!IsValidISADictionary(dictionary_
[kProgramKey
],
514 // Validate the interpreter section (if given).
515 // There must be a matching (portable or sandbox_isa_) entry for interpreter
517 if (dictionary_
.isMember(kInterpreterKey
)) {
518 if (!IsValidISADictionary(dictionary_
[kInterpreterKey
],
527 // Validate the file dictionary (if given).
528 // The "files" key does not require a matching (portable or sandbox_isa_)
529 // entry at schema validation time for NaCl. This allows manifests to specify
530 // resources that are only loaded for a particular sandbox_isa.
531 if (dictionary_
.isMember(kFilesKey
)) {
532 const Json::Value
& files
= dictionary_
[kFilesKey
];
533 if (!files
.isObject()) {
534 error_info
->SetReport(
535 ERROR_MANIFEST_SCHEMA_VALIDATE
,
536 nacl::string("manifest: '") + kFilesKey
+ "' is not a dictionary.");
538 Json::Value::Members members
= files
.getMemberNames();
539 for (size_t i
= 0; i
< members
.size(); ++i
) {
540 nacl::string file_name
= members
[i
];
541 if (!IsValidISADictionary(files
[file_name
],
554 bool JsonManifest::ResolveURL(const nacl::string
& relative_url
,
555 nacl::string
* full_url
,
556 ErrorInfo
* error_info
) const {
557 // The contents of the manifest are resolved relative to the manifest URL.
558 CHECK(url_util_
!= NULL
);
559 pp::Var resolved_url
=
560 url_util_
->ResolveRelativeToURL(pp::Var(manifest_base_url_
),
562 if (!resolved_url
.is_string()) {
563 error_info
->SetReport(
564 ERROR_MANIFEST_RESOLVE_URL
,
565 "could not resolve url '" + relative_url
+
566 "' relative to manifest base url '" + manifest_base_url_
.c_str() +
570 *full_url
= resolved_url
.AsString();
574 bool JsonManifest::GetProgramURL(nacl::string
* full_url
,
575 PnaclOptions
* pnacl_options
,
576 ErrorInfo
* error_info
) const {
577 if (full_url
== NULL
|| pnacl_options
== NULL
|| error_info
== NULL
)
580 Json::Value program
= dictionary_
[kProgramKey
];
582 nacl::string nexe_url
;
583 nacl::string error_string
;
585 if (!GetURLFromISADictionary(program
,
594 return ResolveURL(nexe_url
, full_url
, error_info
);
597 bool JsonManifest::GetFileKeys(std::set
<nacl::string
>* keys
) const {
598 if (!dictionary_
.isMember(kFilesKey
)) {
599 // trivial success: no keys when there is no "files" section.
602 const Json::Value
& files
= dictionary_
[kFilesKey
];
603 CHECK(files
.isObject());
604 Json::Value::Members members
= files
.getMemberNames();
605 for (size_t i
= 0; i
< members
.size(); ++i
) {
606 keys
->insert(members
[i
]);
611 bool JsonManifest::ResolveKey(const nacl::string
& key
,
612 nacl::string
* full_url
,
613 PnaclOptions
* pnacl_options
,
614 ErrorInfo
* error_info
) const {
615 NaClLog(3, "JsonManifest::ResolveKey(%s)\n", key
.c_str());
616 // key must be one of kProgramKey or kFileKey '/' file-section-key
618 if (full_url
== NULL
|| pnacl_options
== NULL
|| error_info
== NULL
)
621 if (key
== kProgramKey
) {
622 return GetKeyUrl(dictionary_
, key
, sandbox_isa_
, this, full_url
,
623 pnacl_options
, error_info
);
625 nacl::string::const_iterator p
= find(key
.begin(), key
.end(), '/');
626 if (p
== key
.end()) {
627 error_info
->SetReport(ERROR_MANIFEST_RESOLVE_URL
,
628 nacl::string("ResolveKey: invalid key, no slash: ")
633 // generalize to permit other sections?
634 nacl::string
prefix(key
.begin(), p
);
635 if (prefix
!= kFilesKey
) {
636 error_info
->SetReport(ERROR_MANIFEST_RESOLVE_URL
,
637 nacl::string("ResolveKey: invalid key: not \"files\""
642 nacl::string
rest(p
+ 1, key
.end());
644 const Json::Value
& files
= dictionary_
[kFilesKey
];
645 if (!files
.isObject()) {
646 error_info
->SetReport(
647 ERROR_MANIFEST_RESOLVE_URL
,
648 nacl::string("ResolveKey: no \"files\" dictionary"));
651 if (!files
.isMember(rest
)) {
652 error_info
->SetReport(
653 ERROR_MANIFEST_RESOLVE_URL
,
654 nacl::string("ResolveKey: no such \"files\" entry: ") + key
);
657 return GetKeyUrl(files
, rest
, sandbox_isa_
, this, full_url
, pnacl_options
,
661 } // namespace plugin