This source file includes following definitions.
- GetNonSFIKey
- FindMatchingProperty
- IsValidDictionary
- IsValidUrlSpec
- IsValidPnaclTranslateSpec
- IsValidISADictionary
- GrabUrlAndPnaclOptions
- Init
- MatchesSchema
- GetURLFromISADictionary
- GetKeyUrl
- ResolveURL
- GetProgramURL
- GetFileKeys
- ResolveKey
#include <algorithm>
#include "ppapi/native_client/src/trusted/plugin/json_manifest.h"
#include <stdlib.h>
#include "native_client/src/include/nacl_base.h"
#include "native_client/src/include/nacl_macros.h"
#include "native_client/src/include/nacl_string.h"
#include "native_client/src/include/portability.h"
#include "native_client/src/shared/platform/nacl_check.h"
#include "ppapi/cpp/dev/url_util_dev.h"
#include "ppapi/cpp/var.h"
#include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
#include "ppapi/native_client/src/trusted/plugin/pnacl_options.h"
#include "ppapi/native_client/src/trusted/plugin/utility.h"
#include "third_party/jsoncpp/source/include/json/reader.h"
namespace plugin {
namespace {
const char* const kProgramKey = "program";
const char* const kInterpreterKey = "interpreter";
const char* const kFilesKey = "files";
const char* const kX8632Key = "x86-32";
const char* const kX8632NonSFIKey = "x86-32-nonsfi";
const char* const kX8664Key = "x86-64";
const char* const kX8664NonSFIKey = "x86-64-nonsfi";
const char* const kArmKey = "arm";
const char* const kArmNonSFIKey = "arm-nonsfi";
const char* const kPortableKey = "portable";
const char* const kPnaclDebugKey = "pnacl-debug";
const char* const kPnaclTranslateKey = "pnacl-translate";
const char* const kUrlKey = "url";
const char* const kOptLevelKey = "optlevel";
nacl::string GetNonSFIKey(const nacl::string& sandbox_isa) {
return sandbox_isa + "-nonsfi";
}
bool FindMatchingProperty(const nacl::string& property_name,
const char** valid_names,
size_t valid_name_count) {
for (size_t i = 0; i < valid_name_count; ++i) {
if (property_name == valid_names[i]) {
return true;
}
}
return false;
}
bool IsValidDictionary(const Json::Value& dictionary,
const nacl::string& container_key,
const nacl::string& parent_key,
const char** valid_keys,
size_t valid_key_count,
const char** required_keys,
size_t required_key_count,
nacl::string* error_string) {
if (!dictionary.isObject()) {
nacl::stringstream error_stream;
error_stream << parent_key << " property '" << container_key
<< "' is non-dictionary value '"
<< dictionary.toStyledString() << "'.";
*error_string = error_stream.str();
return false;
}
Json::Value::Members members = dictionary.getMemberNames();
for (size_t i = 0; i < members.size(); ++i) {
nacl::string property_name = members[i];
if (!FindMatchingProperty(property_name,
valid_keys,
valid_key_count)) {
PLUGIN_PRINTF(("WARNING: '%s' property '%s' has unknown key '%s'.\n",
parent_key.c_str(),
container_key.c_str(), property_name.c_str()));
}
}
for (size_t i = 0; i < required_key_count; ++i) {
if (!dictionary.isMember(required_keys[i])) {
nacl::stringstream error_stream;
error_stream << parent_key << " property '" << container_key
<< "' does not have required key: '"
<< required_keys[i] << "'.";
*error_string = error_stream.str();
return false;
}
}
return true;
}
bool IsValidUrlSpec(const Json::Value& url_spec,
const nacl::string& container_key,
const nacl::string& parent_key,
const nacl::string& sandbox_isa,
nacl::string* error_string) {
static const char* kManifestUrlSpecRequired[] = {
kUrlKey
};
const char** urlSpecPlusOptional;
size_t urlSpecPlusOptionalLength;
if (sandbox_isa == kPortableKey) {
static const char* kPnaclUrlSpecPlusOptional[] = {
kUrlKey,
kOptLevelKey,
};
urlSpecPlusOptional = kPnaclUrlSpecPlusOptional;
urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kPnaclUrlSpecPlusOptional);
} else {
if (url_spec.isMember(kPnaclTranslateKey)) {
nacl::stringstream error_stream;
error_stream << "PNaCl-like NMF with application/x-nacl mimetype instead "
<< "of x-pnacl mimetype (has " << kPnaclTranslateKey << ").";
*error_string = error_stream.str();
return false;
}
urlSpecPlusOptional = kManifestUrlSpecRequired;
urlSpecPlusOptionalLength = NACL_ARRAY_SIZE(kManifestUrlSpecRequired);
}
if (!IsValidDictionary(url_spec, container_key, parent_key,
urlSpecPlusOptional,
urlSpecPlusOptionalLength,
kManifestUrlSpecRequired,
NACL_ARRAY_SIZE(kManifestUrlSpecRequired),
error_string)) {
return false;
}
Json::Value url = url_spec[kUrlKey];
if (!url.isString()) {
nacl::stringstream error_stream;
error_stream << parent_key << " property '" << container_key <<
"' has non-string value '" << url.toStyledString() <<
"' for key '" << kUrlKey << "'.";
*error_string = error_stream.str();
return false;
}
Json::Value opt_level = url_spec[kOptLevelKey];
if (!opt_level.empty() && !opt_level.isNumeric()) {
nacl::stringstream error_stream;
error_stream << parent_key << " property '" << container_key <<
"' has non-numeric value '" << opt_level.toStyledString() <<
"' for key '" << kOptLevelKey << "'.";
*error_string = error_stream.str();
return false;
}
return true;
}
bool IsValidPnaclTranslateSpec(const Json::Value& pnacl_spec,
const nacl::string& container_key,
const nacl::string& parent_key,
const nacl::string& sandbox_isa,
nacl::string* error_string) {
static const char* kManifestPnaclSpecValid[] = {
kPnaclDebugKey,
kPnaclTranslateKey
};
static const char* kManifestPnaclSpecRequired[] = {
kPnaclTranslateKey
};
if (!IsValidDictionary(pnacl_spec, container_key, parent_key,
kManifestPnaclSpecValid,
NACL_ARRAY_SIZE(kManifestPnaclSpecValid),
kManifestPnaclSpecRequired,
NACL_ARRAY_SIZE(kManifestPnaclSpecRequired),
error_string)) {
return false;
}
Json::Value url_spec = pnacl_spec[kPnaclTranslateKey];
if (!IsValidUrlSpec(url_spec, kPnaclTranslateKey,
container_key, sandbox_isa, error_string)) {
return false;
}
return true;
}
bool IsValidISADictionary(const Json::Value& dictionary,
const nacl::string& parent_key,
const nacl::string& sandbox_isa,
bool must_find_matching_entry,
bool nonsfi_enabled,
ErrorInfo* error_info) {
if (error_info == NULL) return false;
if (!dictionary.isObject()) {
error_info->SetReport(PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
nacl::string("manifest: ") + parent_key +
" property is not an ISA to URL dictionary");
return false;
}
const char** isaProperties;
size_t isaPropertiesLength;
if (sandbox_isa == kPortableKey) {
static const char* kPnaclManifestISAProperties[] = {
kPortableKey
};
isaProperties = kPnaclManifestISAProperties;
isaPropertiesLength = NACL_ARRAY_SIZE(kPnaclManifestISAProperties);
} else {
static const char* kNaClManifestISAProperties[] = {
kX8632Key,
kX8632NonSFIKey,
kX8664Key,
kX8664NonSFIKey,
kArmKey,
kArmNonSFIKey,
kPortableKey
};
isaProperties = kNaClManifestISAProperties;
isaPropertiesLength = NACL_ARRAY_SIZE(kNaClManifestISAProperties);
}
Json::Value::Members members = dictionary.getMemberNames();
for (size_t i = 0; i < members.size(); ++i) {
nacl::string property_name = members[i];
Json::Value property_value = dictionary[property_name];
nacl::string error_string;
if (FindMatchingProperty(property_name,
isaProperties,
isaPropertiesLength)) {
if ((sandbox_isa != kPortableKey &&
!IsValidUrlSpec(property_value, property_name, parent_key,
sandbox_isa, &error_string)) ||
(sandbox_isa == kPortableKey &&
parent_key == kProgramKey &&
!IsValidPnaclTranslateSpec(property_value, property_name, parent_key,
sandbox_isa, &error_string)) ||
(sandbox_isa == kPortableKey &&
parent_key != kProgramKey &&
!IsValidUrlSpec(property_value, property_name, parent_key,
sandbox_isa, &error_string))) {
error_info->SetReport(PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
nacl::string("manifest: ") + error_string);
return false;
}
} else {
PLUGIN_PRINTF(("IsValidISADictionary: unrecognized key '%s'.\n",
property_name.c_str()));
if (!IsValidUrlSpec(property_value, property_name, parent_key,
sandbox_isa, &error_string)) {
error_info->SetReport(PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
nacl::string("manifest: ") + error_string);
return false;
}
}
}
if (sandbox_isa == kPortableKey) {
bool has_portable = dictionary.isMember(kPortableKey);
if (!has_portable) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH,
nacl::string("manifest: no version of ") + parent_key +
" given for portable.");
return false;
}
} else if (must_find_matching_entry) {
bool has_isa = dictionary.isMember(sandbox_isa);
bool has_nonsfi_isa =
nonsfi_enabled && dictionary.isMember(GetNonSFIKey(sandbox_isa));
bool has_portable = dictionary.isMember(kPortableKey);
if (!has_isa && !has_nonsfi_isa && !has_portable) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_PROGRAM_MISSING_ARCH,
nacl::string("manifest: no version of ") + parent_key +
" given for current arch and no portable version found.");
return false;
}
}
return true;
}
void GrabUrlAndPnaclOptions(const Json::Value& url_spec,
nacl::string* url,
PnaclOptions* pnacl_options) {
*url = url_spec[kUrlKey].asString();
pnacl_options->set_translate(true);
if (url_spec.isMember(kOptLevelKey)) {
int32_t opt_raw = url_spec[kOptLevelKey].asInt();
pnacl_options->set_opt_level(opt_raw);
}
}
}
bool JsonManifest::Init(const nacl::string& manifest_json,
ErrorInfo* error_info) {
if (error_info == NULL) {
return false;
}
Json::Reader reader;
if (!reader.parse(manifest_json, dictionary_)) {
std::string json_error = reader.getFormatedErrorMessages();
error_info->SetReport(PP_NACL_ERROR_MANIFEST_PARSING,
"manifest JSON parsing failed: " + json_error);
return false;
}
return MatchesSchema(error_info);
}
bool JsonManifest::MatchesSchema(ErrorInfo* error_info) {
pp::Var exception;
if (error_info == NULL) {
return false;
}
if (!dictionary_.isObject()) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
"manifest: is not a json dictionary.");
return false;
}
Json::Value::Members members = dictionary_.getMemberNames();
for (size_t i = 0; i < members.size(); ++i) {
static const char* kManifestTopLevelProperties[] = { kProgramKey,
kInterpreterKey,
kFilesKey };
nacl::string property_name = members[i];
if (!FindMatchingProperty(property_name,
kManifestTopLevelProperties,
NACL_ARRAY_SIZE(kManifestTopLevelProperties))) {
PLUGIN_PRINTF(("JsonManifest::MatchesSchema: WARNING: unknown top-level "
"section '%s' in manifest.\n", property_name.c_str()));
}
}
if (!dictionary_.isMember(kProgramKey)) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
nacl::string("manifest: missing '") + kProgramKey + "' section.");
return false;
}
if (!IsValidISADictionary(dictionary_[kProgramKey],
kProgramKey,
sandbox_isa_,
true,
nonsfi_enabled_,
error_info)) {
return false;
}
if (dictionary_.isMember(kInterpreterKey)) {
if (!IsValidISADictionary(dictionary_[kInterpreterKey],
kInterpreterKey,
sandbox_isa_,
true,
nonsfi_enabled_,
error_info)) {
return false;
}
}
if (dictionary_.isMember(kFilesKey)) {
const Json::Value& files = dictionary_[kFilesKey];
if (!files.isObject()) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_SCHEMA_VALIDATE,
nacl::string("manifest: '") + kFilesKey + "' is not a dictionary.");
}
Json::Value::Members members = files.getMemberNames();
for (size_t i = 0; i < members.size(); ++i) {
nacl::string file_name = members[i];
if (!IsValidISADictionary(files[file_name],
file_name,
sandbox_isa_,
false,
nonsfi_enabled_,
error_info)) {
return false;
}
}
}
return true;
}
bool JsonManifest::GetURLFromISADictionary(const Json::Value& dictionary,
const nacl::string& parent_key,
nacl::string* url,
PnaclOptions* pnacl_options,
bool* uses_nonsfi_mode,
ErrorInfo* error_info) const {
DCHECK(url != NULL && pnacl_options != NULL && error_info != NULL);
if (!IsValidISADictionary(dictionary, parent_key, sandbox_isa_, true,
nonsfi_enabled_, error_info)) {
error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
"architecture " + sandbox_isa_ +
" is not found for file " + parent_key);
return false;
}
*uses_nonsfi_mode = false;
nacl::string chosen_isa;
if (sandbox_isa_ == kPortableKey) {
chosen_isa = kPortableKey;
} else {
nacl::string nonsfi_isa = GetNonSFIKey(sandbox_isa_);
if (nonsfi_enabled_ && dictionary.isMember(nonsfi_isa)) {
chosen_isa = nonsfi_isa;
*uses_nonsfi_mode = true;
} else if (dictionary.isMember(sandbox_isa_)) {
chosen_isa = sandbox_isa_;
} else if (dictionary.isMember(kPortableKey)) {
chosen_isa = kPortableKey;
} else {
DCHECK(false);
}
}
const Json::Value& isa_spec = dictionary[chosen_isa];
if (pnacl_debug_ && isa_spec.isMember(kPnaclDebugKey)) {
GrabUrlAndPnaclOptions(isa_spec[kPnaclDebugKey], url, pnacl_options);
pnacl_options->set_debug(true);
} else if (isa_spec.isMember(kPnaclTranslateKey)) {
GrabUrlAndPnaclOptions(isa_spec[kPnaclTranslateKey], url, pnacl_options);
} else {
*url = isa_spec[kUrlKey].asString();
pnacl_options->set_translate(false);
}
return true;
}
bool JsonManifest::GetKeyUrl(const Json::Value& dictionary,
const nacl::string& key,
nacl::string* full_url,
PnaclOptions* pnacl_options,
ErrorInfo* error_info) const {
DCHECK(full_url != NULL && pnacl_options != NULL && error_info != NULL);
if (!dictionary.isMember(key)) {
error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
"file key not found in manifest");
return false;
}
const Json::Value& isa_dict = dictionary[key];
nacl::string relative_url;
bool uses_nonsfi_mode;
if (!GetURLFromISADictionary(isa_dict, key, &relative_url,
pnacl_options, &uses_nonsfi_mode, error_info)) {
return false;
}
return ResolveURL(relative_url, full_url, error_info);
}
bool JsonManifest::ResolveURL(const nacl::string& relative_url,
nacl::string* full_url,
ErrorInfo* error_info) const {
CHECK(url_util_ != NULL);
pp::Var resolved_url =
url_util_->ResolveRelativeToURL(pp::Var(manifest_base_url_),
relative_url);
if (!resolved_url.is_string()) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
"could not resolve url '" + relative_url +
"' relative to manifest base url '" + manifest_base_url_.c_str() +
"'.");
return false;
}
*full_url = resolved_url.AsString();
return true;
}
bool JsonManifest::GetProgramURL(nacl::string* full_url,
PnaclOptions* pnacl_options,
bool* uses_nonsfi_mode,
ErrorInfo* error_info) const {
if (full_url == NULL || pnacl_options == NULL || error_info == NULL)
return false;
const Json::Value& program = dictionary_[kProgramKey];
nacl::string nexe_url;
nacl::string error_string;
if (!GetURLFromISADictionary(program,
kProgramKey,
&nexe_url,
pnacl_options,
uses_nonsfi_mode,
error_info)) {
return false;
}
return ResolveURL(nexe_url, full_url, error_info);
}
bool JsonManifest::GetFileKeys(std::set<nacl::string>* keys) const {
if (!dictionary_.isMember(kFilesKey)) {
return true;
}
const Json::Value& files = dictionary_[kFilesKey];
CHECK(files.isObject());
Json::Value::Members members = files.getMemberNames();
for (size_t i = 0; i < members.size(); ++i) {
keys->insert(members[i]);
}
return true;
}
bool JsonManifest::ResolveKey(const nacl::string& key,
nacl::string* full_url,
PnaclOptions* pnacl_options,
ErrorInfo* error_info) const {
NaClLog(3, "JsonManifest::ResolveKey(%s)\n", key.c_str());
if (full_url == NULL || pnacl_options == NULL || error_info == NULL)
return false;
if (key == kProgramKey) {
return GetKeyUrl(dictionary_, key, full_url, pnacl_options, error_info);
}
nacl::string::const_iterator p = find(key.begin(), key.end(), '/');
if (p == key.end()) {
error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
nacl::string("ResolveKey: invalid key, no slash: ")
+ key);
return false;
}
nacl::string prefix(key.begin(), p);
if (prefix != kFilesKey) {
error_info->SetReport(PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
nacl::string("ResolveKey: invalid key: not \"files\""
" prefix: ") + key);
return false;
}
nacl::string rest(p + 1, key.end());
const Json::Value& files = dictionary_[kFilesKey];
if (!files.isObject()) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
nacl::string("ResolveKey: no \"files\" dictionary"));
return false;
}
if (!files.isMember(rest)) {
error_info->SetReport(
PP_NACL_ERROR_MANIFEST_RESOLVE_URL,
nacl::string("ResolveKey: no such \"files\" entry: ") + key);
return false;
}
return GetKeyUrl(files, rest, full_url, pnacl_options, error_info);
}
}