Bug 1861819 - Use JS::ParseJSONWithHandler in BasePrincipal. r=peterv

This avoid intermediate object tree for "JSON to principal" case.

Differential Revision: https://phabricator.services.mozilla.com/D192146
This commit is contained in:
Tooru Fujisawa 2024-01-26 15:39:39 +00:00
parent 081d863ef2
commit 31ceef48aa
16 changed files with 1271 additions and 426 deletions

View File

@ -44,9 +44,15 @@
#include "nsIURIMutator.h"
#include "mozilla/PermissionManager.h"
#include "json/json.h"
#include "nsSerializationHelper.h"
#include "js/JSON.h"
#include "ContentPrincipalJSONHandler.h"
#include "ExpandedPrincipalJSONHandler.h"
#include "NullPrincipalJSONHandler.h"
#include "PrincipalJSONHandler.h"
#include "SubsumedPrincipalJSONHandler.h"
namespace mozilla {
BasePrincipal::BasePrincipal(PrincipalKind aKind,
@ -136,113 +142,183 @@ BasePrincipal::GetSiteOriginNoSuffix(nsACString& aSiteOrigin) {
return GetOriginNoSuffix(aSiteOrigin);
}
// Returns the inner Json::value of the serialized principal
// Example input and return values:
// Null principal:
// {"0":{"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}} ->
// {"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}
//
// Content principal:
// {"1":{"0":"https://mozilla.com"}} -> {"0":"https://mozilla.com"}
//
// Expanded principal:
// {"2":{"0":"<base64principal1>,<base64principal2>"}} ->
// {"0":"<base64principal1>,<base64principal2>"}
//
// System principal:
// {"3":{}} -> {}
// The aKey passed in also returns the corresponding PrincipalKind enum
//
// Warning: The Json::Value* pointer is into the aRoot object
static const Json::Value* GetPrincipalObject(const Json::Value& aRoot,
int& aOutPrincipalKind) {
const Json::Value::Members members = aRoot.getMemberNames();
// We only support one top level key in the object
if (members.size() != 1) {
return nullptr;
template <typename HandlerTypesT>
bool ContainerPrincipalJSONHandler<HandlerTypesT>::ProcessInnerResult(
bool aResult) {
if (!aResult) {
NS_WARNING("Failed to parse inner object");
mState = State::Error;
return false;
}
// members[0] here is the "0", "1", "2", "3" principalKind
// that is the top level of the serialized JSON principal
const std::string stringPrincipalKind = members[0];
// Next we take the string value from the JSON
// and convert it into the int for the BasePrincipal::PrincipalKind enum
// Verify that the key is within the valid range
int principalKind = std::stoi(stringPrincipalKind);
MOZ_ASSERT(BasePrincipal::eNullPrincipal == 0,
"We need to rely on 0 being a bounds check for the first "
"principal kind.");
if (principalKind < 0 || principalKind > BasePrincipal::eKindMax) {
return nullptr;
}
MOZ_ASSERT(principalKind == BasePrincipal::eNullPrincipal ||
principalKind == BasePrincipal::eContentPrincipal ||
principalKind == BasePrincipal::eExpandedPrincipal ||
principalKind == BasePrincipal::eSystemPrincipal);
aOutPrincipalKind = principalKind;
if (!aRoot[stringPrincipalKind].isObject()) {
return nullptr;
}
// Return the inner value of the principal object
return &aRoot[stringPrincipalKind];
return true;
}
// Accepts the JSON inner object without the wrapping principalKind
// (See GetPrincipalObject for the inner object response examples)
// Creates an array of KeyVal objects that are all defined on the principal
// Each principal type (null, content, expanded) has a KeyVal that stores the
// fields of the JSON
//
// This simplifies deserializing elsewhere as we do the checking for presence
// and string values here for the complete set of serializable keys that the
// corresponding principal supports.
//
// The KeyVal object has the following fields:
// - valueWasSerialized: is true if the deserialized JSON contained a string
// value
// - value: The string that was serialized for this key
// - key: an SerializableKeys enum value specific to the principal.
// For example content principal is an enum of: eURI, eDomain,
// eSuffix, eCSP
//
//
// Given an inner content principal:
// {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}
// | | | |
// ----------------------------- |
// | | |
// Key ----------------------
// |
// Value
//
// They Key "0" corresponds to ContentPrincipal::eURI
// They Key "1" corresponds to ContentPrincipal::eSuffix
template <typename T>
static nsTArray<typename T::KeyVal> GetJSONKeys(const Json::Value* aInput) {
int size = T::eMax + 1;
nsTArray<typename T::KeyVal> fields;
for (int i = 0; i != size; i++) {
typename T::KeyVal* field = fields.AppendElement();
// field->valueWasSerialized returns if the field was found in the
// deserialized code. This simplifies the consumers from having to check
// length.
field->valueWasSerialized = false;
field->key = static_cast<typename T::SerializableKeys>(i);
const std::string key = std::to_string(field->key);
if (aInput->isMember(key)) {
const Json::Value& val = (*aInput)[key];
if (val.isString()) {
field->value.Append(nsDependentCString(val.asCString()));
field->valueWasSerialized = true;
template <typename HandlerTypesT>
bool ContainerPrincipalJSONHandler<HandlerTypesT>::startObject() {
if (mInnerHandler.isSome()) {
return CallOnInner([&](auto& aInner) { return aInner.startObject(); });
}
switch (mState) {
case State::Init:
mState = State::StartObject;
break;
case State::SystemPrincipal_Key:
mState = State::SystemPrincipal_StartObject;
break;
default:
NS_WARNING("Unexpected object value");
mState = State::Error;
return false;
}
return true;
}
template <typename HandlerTypesT>
bool ContainerPrincipalJSONHandler<HandlerTypesT>::propertyName(
const JS::Latin1Char* name, size_t length) {
if (mInnerHandler.isSome()) {
return CallOnInner(
[&](auto& aInner) { return aInner.propertyName(name, length); });
}
switch (mState) {
case State::StartObject: {
if (length != 1) {
NS_WARNING(
nsPrintfCString("Unexpected property name length: %zu", length)
.get());
mState = State::Error;
return false;
}
char key = char(name[0]);
switch (key) {
case BasePrincipal::NullPrincipalKey:
mState = State::NullPrincipal_Inner;
mInnerHandler.emplace(VariantType<NullPrincipalJSONHandler>());
break;
case BasePrincipal::ContentPrincipalKey:
mState = State::ContentPrincipal_Inner;
mInnerHandler.emplace(VariantType<ContentPrincipalJSONHandler>());
break;
case BasePrincipal::SystemPrincipalKey:
mState = State::SystemPrincipal_Key;
break;
default:
if constexpr (CanContainExpandedPrincipal) {
if (key == BasePrincipal::ExpandedPrincipalKey) {
mState = State::ExpandedPrincipal_Inner;
mInnerHandler.emplace(
VariantType<ExpandedPrincipalJSONHandler>());
break;
}
}
NS_WARNING(
nsPrintfCString("Unexpected property name: '%c'", key).get());
mState = State::Error;
return false;
}
break;
}
default:
NS_WARNING("Unexpected property name");
mState = State::Error;
return false;
}
return true;
}
template <typename HandlerTypesT>
bool ContainerPrincipalJSONHandler<HandlerTypesT>::endObject() {
if (mInnerHandler.isSome()) {
return CallOnInner([&](auto& aInner) {
if (!aInner.endObject()) {
return false;
}
if (aInner.HasAccepted()) {
this->mPrincipal = aInner.mPrincipal.forget();
MOZ_ASSERT(this->mPrincipal);
mInnerHandler.reset();
}
return true;
});
}
switch (mState) {
case State::SystemPrincipal_StartObject:
mState = State::SystemPrincipal_EndObject;
break;
case State::SystemPrincipal_EndObject:
this->mPrincipal =
BasePrincipal::Cast(nsContentUtils::GetSystemPrincipal());
mState = State::EndObject;
break;
case State::NullPrincipal_Inner:
mState = State::EndObject;
break;
case State::ContentPrincipal_Inner:
mState = State::EndObject;
break;
default:
if constexpr (CanContainExpandedPrincipal) {
if (mState == State::ExpandedPrincipal_Inner) {
mState = State::EndObject;
break;
}
}
NS_WARNING("Unexpected end of object");
mState = State::Error;
return false;
}
return true;
}
template <typename HandlerTypesT>
bool ContainerPrincipalJSONHandler<HandlerTypesT>::startArray() {
if constexpr (CanContainExpandedPrincipal) {
if (mInnerHandler.isSome()) {
return CallOnInner([&](auto& aInner) { return aInner.startArray(); });
}
}
return fields;
NS_WARNING("Unexpected array value");
mState = State::Error;
return false;
}
template <typename HandlerTypesT>
bool ContainerPrincipalJSONHandler<HandlerTypesT>::endArray() {
if constexpr (CanContainExpandedPrincipal) {
if (mInnerHandler.isSome()) {
return CallOnInner([&](auto& aInner) { return aInner.endArray(); });
}
}
NS_WARNING("Unexpected array value");
mState = State::Error;
return false;
}
template <typename HandlerTypesT>
bool ContainerPrincipalJSONHandler<HandlerTypesT>::stringValue(
const JS::Latin1Char* str, size_t length) {
if (mInnerHandler.isSome()) {
return CallOnInner(
[&](auto& aInner) { return aInner.stringValue(str, length); });
}
NS_WARNING("Unexpected string value");
mState = State::Error;
return false;
}
template class ContainerPrincipalJSONHandler<PrincipalJSONHandlerTypes>;
template class ContainerPrincipalJSONHandler<SubsumedPrincipalJSONHandlerTypes>;
// Takes a JSON string and parses it turning it into a principal of the
// corresponding type
//
@ -261,110 +337,21 @@ static nsTArray<typename T::KeyVal> GetJSONKeys(const Json::Value* aInput) {
// SerializableKeys |
// Value
//
// The string is first deserialized with jsoncpp to get the Json::Value of the
// object. The inner JSON object is parsed with GetPrincipalObject which returns
// a KeyVal array of the inner object's fields. PrincipalKind is returned by
// GetPrincipalObject which is then used to decide which principal
// implementation of FromProperties to call. The corresponding FromProperties
// call takes the KeyVal fields and turns it into a principal.
already_AddRefed<BasePrincipal> BasePrincipal::FromJSON(
const nsACString& aJSON) {
Json::Value root;
Json::CharReaderBuilder builder;
std::unique_ptr<Json::CharReader> const reader(builder.newCharReader());
bool parseSuccess =
reader->parse(aJSON.BeginReading(), aJSON.EndReading(), &root, nullptr);
if (!parseSuccess) {
PrincipalJSONHandler handler;
if (!JS::ParseJSONWithHandler(
reinterpret_cast<const JS::Latin1Char*>(aJSON.BeginReading()),
aJSON.Length(), &handler)) {
NS_WARNING(
nsPrintfCString("Unable to parse: %s", aJSON.BeginReading()).get());
MOZ_ASSERT(false,
"Unable to parse string as JSON to deserialize as a principal");
return nullptr;
}
return FromJSON(root);
}
// Checks if an ExpandedPrincipal is using the legacy format, where
// sub-principals are Base64 encoded.
//
// Given a legacy expanded principal:
//
// *
// {"2": {"0": "eyIxIjp7IjAiOiJodHRwczovL2EuY29tLyJ9fQ=="}}
// | | |
// | ---------- Value
// | |
// PrincipalKind |
// |
// SerializableKeys
//
// The value is a CSV list of Base64 encoded prinipcals. The new format for this
// principal is:
//
// Subsumed principals
// |
// ------------------------------------
// * | |
// {"2": {"0": [{"1": {"0": https://mozilla.com"}}]}}
// | | |
// -------------- Value
// |
// PrincipalKind
//
// It is possible to tell these apart by checking the type of the property noted
// in both diagrams with an asterisk. In the legacy format the type will be a
// string and in the new format it will be an array.
static bool IsLegacyFormat(const Json::Value& aValue) {
const auto& specs = std::to_string(ExpandedPrincipal::eSpecs);
return aValue.isMember(specs) && aValue[specs].isString();
}
/* static */
already_AddRefed<BasePrincipal> BasePrincipal::FromJSON(
const Json::Value& aJSON) {
int principalKind = -1;
const Json::Value* value = GetPrincipalObject(aJSON, principalKind);
if (!value) {
#ifdef DEBUG
fprintf(stderr, "Unexpected JSON principal %s\n",
aJSON.toStyledString().c_str());
#endif
MOZ_ASSERT(false, "Unexpected JSON to deserialize as a principal");
return nullptr;
}
MOZ_ASSERT(principalKind != -1,
"PrincipalKind should always be >=0 by this point");
if (principalKind == eSystemPrincipal) {
RefPtr<BasePrincipal> principal =
BasePrincipal::Cast(nsContentUtils::GetSystemPrincipal());
return principal.forget();
}
if (principalKind == eNullPrincipal) {
nsTArray<NullPrincipal::KeyVal> res = GetJSONKeys<NullPrincipal>(value);
return NullPrincipal::FromProperties(res);
}
if (principalKind == eContentPrincipal) {
nsTArray<ContentPrincipal::KeyVal> res =
GetJSONKeys<ContentPrincipal>(value);
return ContentPrincipal::FromProperties(res);
}
if (principalKind == eExpandedPrincipal) {
// Check if expanded principals is stored in the new or the old format. See
// comment for `IsLegacyFormat`.
if (IsLegacyFormat(*value)) {
nsTArray<ExpandedPrincipal::KeyVal> res =
GetJSONKeys<ExpandedPrincipal>(value);
return ExpandedPrincipal::FromProperties(res);
}
return ExpandedPrincipal::FromProperties(*value);
}
MOZ_RELEASE_ASSERT(false, "Unexpected enum to deserialize as a principal");
return handler.Get();
}
// Returns a JSON representation of the principal.

View File

@ -27,9 +27,6 @@ class nsIChannel;
class nsIReferrerInfo;
class nsISupports;
class nsIURI;
namespace Json {
class Value;
}
namespace mozilla {
@ -93,6 +90,15 @@ class BasePrincipal : public nsJSPrincipals {
eKindMax = eSystemPrincipal
};
static constexpr char NullPrincipalKey = '0';
static_assert(eNullPrincipal == 0);
static constexpr char ContentPrincipalKey = '1';
static_assert(eContentPrincipal == 1);
static constexpr char ExpandedPrincipalKey = '2';
static_assert(eExpandedPrincipal == 2);
static constexpr char SystemPrincipalKey = '3';
static_assert(eSystemPrincipal == 3);
template <typename T>
bool Is() const {
return mKind == T::Kind();
@ -195,7 +201,6 @@ class BasePrincipal : public nsJSPrincipals {
nsresult WriteJSONProperties(JSONWriter& aWriter);
static already_AddRefed<BasePrincipal> FromJSON(const nsACString& aJSON);
static already_AddRefed<BasePrincipal> FromJSON(const Json::Value& aJSON);
// Method to write serializable fields which represent all of the fields to
// deserialize the principal.

View File

@ -39,6 +39,9 @@
#include "nsSerializationHelper.h"
#include "js/JSON.h"
#include "ContentPrincipalJSONHandler.h"
using namespace mozilla;
NS_IMPL_CLASSINFO(ContentPrincipal, nullptr, 0, NS_PRINCIPAL_CID)
@ -643,66 +646,147 @@ nsresult ContentPrincipal::WriteJSONInnerProperties(JSONWriter& aWriter) {
return NS_OK;
}
already_AddRefed<BasePrincipal> ContentPrincipal::FromProperties(
nsTArray<ContentPrincipal::KeyVal>& aFields) {
MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys");
nsresult rv;
nsCOMPtr<nsIURI> principalURI;
nsCOMPtr<nsIURI> domain;
nsCOMPtr<nsIContentSecurityPolicy> csp;
OriginAttributes attrs;
// The odd structure here is to make the code to not compile
// if all the switch enum cases haven't been codified
for (const auto& field : aFields) {
switch (field.key) {
case ContentPrincipal::eURI:
if (!field.valueWasSerialized) {
MOZ_ASSERT(
false,
"Content principals require a principal URI in serialized JSON");
return nullptr;
}
rv = NS_NewURI(getter_AddRefs(principalURI), field.value.get());
NS_ENSURE_SUCCESS(rv, nullptr);
{
// Enforce re-parsing about: URIs so that if they change, we
// continue to use their new principals correctly.
if (principalURI->SchemeIs("about")) {
nsAutoCString spec;
principalURI->GetSpec(spec);
if (NS_FAILED(NS_NewURI(getter_AddRefs(principalURI), spec))) {
return nullptr;
}
}
}
break;
case ContentPrincipal::eDomain:
if (field.valueWasSerialized) {
rv = NS_NewURI(getter_AddRefs(domain), field.value.get());
NS_ENSURE_SUCCESS(rv, nullptr);
}
break;
case ContentPrincipal::eSuffix:
if (field.valueWasSerialized) {
bool ok = attrs.PopulateFromSuffix(field.value);
if (!ok) {
return nullptr;
}
}
break;
}
}
nsAutoCString originNoSuffix;
rv = ContentPrincipal::GenerateOriginNoSuffixFromURI(principalURI,
originNoSuffix);
if (NS_FAILED(rv)) {
return nullptr;
bool ContentPrincipalJSONHandler::startObject() {
switch (mState) {
case State::Init:
mState = State::StartObject;
break;
default:
NS_WARNING("Unexpected object value");
mState = State::Error;
return false;
}
RefPtr<ContentPrincipal> principal =
new ContentPrincipal(principalURI, attrs, originNoSuffix, domain);
return principal.forget();
return true;
}
bool ContentPrincipalJSONHandler::propertyName(const JS::Latin1Char* name,
size_t length) {
switch (mState) {
case State::StartObject:
case State::AfterPropertyValue: {
if (length != 1) {
NS_WARNING(
nsPrintfCString("Unexpected property name length: %zu", length)
.get());
mState = State::Error;
return false;
}
char key = char(name[0]);
switch (key) {
case ContentPrincipal::URIKey:
mState = State::URIKey;
break;
case ContentPrincipal::DomainKey:
mState = State::DomainKey;
break;
case ContentPrincipal::SuffixKey:
mState = State::SuffixKey;
break;
default:
NS_WARNING(
nsPrintfCString("Unexpected property name: '%c'", key).get());
mState = State::Error;
return false;
}
break;
}
default:
NS_WARNING("Unexpected property name");
mState = State::Error;
return false;
}
return true;
}
bool ContentPrincipalJSONHandler::endObject() {
switch (mState) {
case State::AfterPropertyValue: {
MOZ_ASSERT(mPrincipalURI);
// NOTE: mDomain is optional.
nsAutoCString originNoSuffix;
nsresult rv = ContentPrincipal::GenerateOriginNoSuffixFromURI(
mPrincipalURI, originNoSuffix);
if (NS_FAILED(rv)) {
mState = State::Error;
return false;
}
mPrincipal =
new ContentPrincipal(mPrincipalURI, mAttrs, originNoSuffix, mDomain);
MOZ_ASSERT(mPrincipal);
mState = State::EndObject;
break;
}
default:
NS_WARNING("Unexpected end of object");
mState = State::Error;
return false;
}
return true;
}
bool ContentPrincipalJSONHandler::stringValue(const JS::Latin1Char* str,
size_t length) {
switch (mState) {
case State::URIKey: {
nsDependentCSubstring spec(reinterpret_cast<const char*>(str), length);
nsresult rv = NS_NewURI(getter_AddRefs(mPrincipalURI), spec);
if (NS_FAILED(rv)) {
mState = State::Error;
return false;
}
{
// Enforce re-parsing about: URIs so that if they change, we
// continue to use their new principals correctly.
if (mPrincipalURI->SchemeIs("about")) {
nsAutoCString spec;
mPrincipalURI->GetSpec(spec);
rv = NS_NewURI(getter_AddRefs(mPrincipalURI), spec);
if (NS_FAILED(rv)) {
mState = State::Error;
return false;
}
}
}
mState = State::AfterPropertyValue;
break;
}
case State::DomainKey: {
nsDependentCSubstring spec(reinterpret_cast<const char*>(str), length);
nsresult rv = NS_NewURI(getter_AddRefs(mDomain), spec);
if (NS_FAILED(rv)) {
mState = State::Error;
return false;
}
mState = State::AfterPropertyValue;
break;
}
case State::SuffixKey: {
nsDependentCSubstring attrs(reinterpret_cast<const char*>(str), length);
if (!mAttrs.PopulateFromSuffix(attrs)) {
mState = State::Error;
return false;
}
mState = State::AfterPropertyValue;
break;
}
default:
NS_WARNING("Unexpected string value");
mState = State::Error;
return false;
}
return true;
}

View File

@ -56,10 +56,13 @@ class ContentPrincipal final : public BasePrincipal {
eSuffix,
eMax = eSuffix
};
typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal;
static already_AddRefed<BasePrincipal> FromProperties(
nsTArray<ContentPrincipal::KeyVal>& aFields);
static constexpr char URIKey = '0';
static_assert(eURI == 0);
static constexpr char DomainKey = '1';
static_assert(eDomain == 1);
static constexpr char SuffixKey = '2';
static_assert(eSuffix == 2);
class Deserializer : public BasePrincipal::Deserializer {
public:

View File

@ -0,0 +1,94 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ContentPrincipalJSONHandler_h
#define mozilla_ContentPrincipalJSONHandler_h
#include <stddef.h> // size_t
#include <stdint.h> // uint32_t
#include "js/TypeDecls.h" // JS::Latin1Char
#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
#include "mozilla/RefPtr.h" // RefPtr
#include "nsCOMPtr.h" // nsCOMPtr
#include "nsDebug.h" // NS_WARNING
#include "nsIURI.h" // nsIURI
#include "ContentPrincipal.h"
#include "OriginAttributes.h"
#include "SharedJSONHandler.h"
namespace mozilla {
// JSON parse handler for an inner object for ContentPrincipal.
// Used by PrincipalJSONHandler or SubsumedPrincipalJSONHandler.
class ContentPrincipalJSONHandler : public PrincipalJSONHandlerShared {
enum class State {
Init,
// After the inner object's '{'.
StartObject,
// After the property key for eURI.
URIKey,
// After the property key for eDomain.
DomainKey,
// After the property key for eSuffix.
SuffixKey,
// After the property value for eURI, eDomain, or eSuffix.
AfterPropertyValue,
// After the inner object's '}'.
EndObject,
Error,
};
public:
ContentPrincipalJSONHandler() = default;
virtual ~ContentPrincipalJSONHandler() = default;
virtual bool startObject() override;
using PrincipalJSONHandlerShared::propertyName;
virtual bool propertyName(const JS::Latin1Char* name, size_t length) override;
virtual bool endObject() override;
virtual bool startArray() override {
NS_WARNING("Unexpected array value");
mState = State::Error;
return false;
}
virtual bool endArray() override {
NS_WARNING("Unexpected array value");
mState = State::Error;
return false;
}
using PrincipalJSONHandlerShared::stringValue;
virtual bool stringValue(const JS::Latin1Char* str, size_t length) override;
bool HasAccepted() const { return mState == State::EndObject; }
protected:
virtual void SetErrorState() override { mState = State::Error; }
private:
State mState = State::Init;
nsCOMPtr<nsIURI> mPrincipalURI;
nsCOMPtr<nsIURI> mDomain;
OriginAttributes mAttrs;
};
} // namespace mozilla
#endif // mozilla_ContentPrincipalJSONHandler_h

View File

@ -11,7 +11,10 @@
#include "mozilla/Base64.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/JSONWriter.h"
#include "json/json.h"
#include "js/JSON.h"
#include "ExpandedPrincipalJSONHandler.h"
#include "SubsumedPrincipalJSONHandler.h"
using namespace mozilla;
@ -287,100 +290,187 @@ nsresult ExpandedPrincipal::WriteJSONInnerProperties(JSONWriter& aWriter) {
return NS_OK;
}
already_AddRefed<BasePrincipal> ExpandedPrincipal::FromProperties(
nsTArray<ExpandedPrincipal::KeyVal>& aFields) {
MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys");
nsTArray<nsCOMPtr<nsIPrincipal>> allowList;
OriginAttributes attrs;
// The odd structure here is to make the code to not compile
// if all the switch enum cases haven't been codified
for (const auto& field : aFields) {
switch (field.key) {
case ExpandedPrincipal::eSpecs:
if (!field.valueWasSerialized) {
MOZ_ASSERT(false,
"Expanded principals require specs in serialized JSON");
return nullptr;
}
for (const nsACString& each : field.value.Split(',')) {
nsAutoCString result;
nsresult rv;
rv = Base64Decode(each, result);
MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to decode");
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(result);
allowList.AppendElement(principal);
}
break;
case ExpandedPrincipal::eSuffix:
if (field.valueWasSerialized) {
bool ok = attrs.PopulateFromSuffix(field.value);
if (!ok) {
return nullptr;
}
}
break;
}
bool ExpandedPrincipalJSONHandler::ProcessSubsumedResult(bool aResult) {
if (!aResult) {
NS_WARNING("Failed to parse subsumed principal");
mState = State::Error;
return false;
}
if (allowList.Length() == 0) {
return nullptr;
}
RefPtr<ExpandedPrincipal> expandedPrincipal =
ExpandedPrincipal::Create(allowList, attrs);
return expandedPrincipal.forget();
return true;
}
/* static */
already_AddRefed<BasePrincipal> ExpandedPrincipal::FromProperties(
const Json::Value& aJSON) {
MOZ_ASSERT(aJSON.size() <= eMax + 1, "Must have at most, all the properties");
const std::string specs = std::to_string(eSpecs);
const std::string suffix = std::to_string(eSuffix);
MOZ_ASSERT(aJSON.isMember(specs), "The eSpecs member is required");
MOZ_ASSERT(aJSON.size() == 1 || aJSON.isMember(suffix),
"eSuffix is optional");
const auto* specsValue =
aJSON.find(specs.c_str(), specs.c_str() + specs.length());
if (!specsValue) {
MOZ_ASSERT(false, "Expanded principals require specs in serialized JSON");
return nullptr;
bool ExpandedPrincipalJSONHandler::startObject() {
if (mSubsumedHandler.isSome()) {
return ProcessSubsumedResult(mSubsumedHandler->startObject());
}
nsTArray<nsCOMPtr<nsIPrincipal>> allowList;
for (const auto& principalJSON : *specsValue) {
if (nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::FromJSON(principalJSON)) {
allowList.AppendElement(principal);
switch (mState) {
case State::Init:
mState = State::StartObject;
break;
case State::StartArray:
mState = State::SubsumedPrincipal;
[[fallthrough]];
case State::SubsumedPrincipal:
mSubsumedHandler.emplace();
return ProcessSubsumedResult(mSubsumedHandler->startObject());
default:
NS_WARNING("Unexpected object value");
mState = State::Error;
return false;
}
return true;
}
bool ExpandedPrincipalJSONHandler::propertyName(const JS::Latin1Char* name,
size_t length) {
if (mSubsumedHandler.isSome()) {
return ProcessSubsumedResult(mSubsumedHandler->propertyName(name, length));
}
switch (mState) {
case State::StartObject:
case State::AfterPropertyValue: {
if (length != 1) {
NS_WARNING(
nsPrintfCString("Unexpected property name length: %zu", length)
.get());
mState = State::Error;
return false;
}
char key = char(name[0]);
switch (key) {
case ExpandedPrincipal::SpecsKey:
mState = State::SpecsKey;
break;
case ExpandedPrincipal::SuffixKey:
mState = State::SuffixKey;
break;
default:
NS_WARNING(
nsPrintfCString("Unexpected property name: '%c'", key).get());
mState = State::Error;
return false;
}
break;
}
default:
NS_WARNING("Unexpected property name");
mState = State::Error;
return false;
}
if (allowList.Length() == 0) {
return nullptr;
}
return true;
}
OriginAttributes attrs;
if (aJSON.isMember(suffix)) {
const auto& value = aJSON[suffix];
if (!value.isString()) {
return nullptr;
bool ExpandedPrincipalJSONHandler::endObject() {
if (mSubsumedHandler.isSome()) {
if (!ProcessSubsumedResult(mSubsumedHandler->endObject())) {
return false;
}
bool ok = attrs.PopulateFromSuffix(nsDependentCString(value.asCString()));
if (!ok) {
return nullptr;
if (mSubsumedHandler->HasAccepted()) {
nsCOMPtr<nsIPrincipal> principal = mSubsumedHandler->mPrincipal.forget();
mSubsumedHandler.reset();
mAllowList.AppendElement(principal);
}
return true;
}
RefPtr<ExpandedPrincipal> expandedPrincipal =
ExpandedPrincipal::Create(allowList, attrs);
switch (mState) {
case State::AfterPropertyValue:
mPrincipal = ExpandedPrincipal::Create(mAllowList, mAttrs);
MOZ_ASSERT(mPrincipal);
return expandedPrincipal.forget();
mState = State::EndObject;
break;
default:
NS_WARNING("Unexpected end of object");
mState = State::Error;
return false;
}
return true;
}
bool ExpandedPrincipalJSONHandler::startArray() {
switch (mState) {
case State::SpecsKey:
mState = State::StartArray;
break;
default:
NS_WARNING("Unexpected array value");
mState = State::Error;
return false;
}
return true;
}
bool ExpandedPrincipalJSONHandler::endArray() {
switch (mState) {
case State::SubsumedPrincipal: {
mState = State::AfterPropertyValue;
break;
}
default:
NS_WARNING("Unexpected end of array");
mState = State::Error;
return false;
}
return true;
}
bool ExpandedPrincipalJSONHandler::stringValue(const JS::Latin1Char* str,
size_t length) {
if (mSubsumedHandler.isSome()) {
return ProcessSubsumedResult(mSubsumedHandler->stringValue(str, length));
}
switch (mState) {
case State::SpecsKey: {
nsDependentCSubstring specs(reinterpret_cast<const char*>(str), length);
for (const nsACString& each : specs.Split(',')) {
nsAutoCString result;
nsresult rv = Base64Decode(each, result);
MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to decode");
if (NS_FAILED(rv)) {
mState = State::Error;
return false;
}
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::FromJSON(result);
if (!principal) {
mState = State::Error;
return false;
}
mAllowList.AppendElement(principal);
}
mState = State::AfterPropertyValue;
break;
}
case State::SuffixKey: {
nsDependentCSubstring attrs(reinterpret_cast<const char*>(str), length);
if (!mAttrs.PopulateFromSuffix(attrs)) {
mState = State::Error;
return false;
}
mState = State::AfterPropertyValue;
break;
}
default:
NS_WARNING("Unexpected string value");
mState = State::Error;
return false;
}
return true;
}
NS_IMETHODIMP

View File

@ -15,10 +15,6 @@
class nsIContentSecurityPolicy;
namespace Json {
class Value;
}
namespace mozilla {
class JSONWriter;
} // namespace mozilla
@ -66,17 +62,11 @@ class ExpandedPrincipal : public nsIExpandedPrincipal,
// Serializable keys are the valid enum fields the serialization supports
enum SerializableKeys : uint8_t { eSpecs = 0, eSuffix, eMax = eSuffix };
typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal;
// This is the legacy serializer for expanded principals. See note for
// `IsLegacyFormat` in BasePrincipal.cpp.
static already_AddRefed<BasePrincipal> FromProperties(
nsTArray<ExpandedPrincipal::KeyVal>& aFields);
// This is the new serializer for expanded principals. See note for
// `IsLegacyFormat` in BasePrincipal.cpp.
static already_AddRefed<BasePrincipal> FromProperties(
const Json::Value& aJSON);
static constexpr char SpecsKey = '0';
static_assert(eSpecs == 0);
static constexpr char SuffixKey = '1';
static_assert(eSuffix == 1);
class Deserializer : public BasePrincipal::Deserializer {
public:

View File

@ -0,0 +1,130 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_ExpandedPrincipalJSONHandler_h
#define mozilla_ExpandedPrincipalJSONHandler_h
#include <stddef.h> // size_t
#include <stdint.h> // uint32_t
#include "js/TypeDecls.h" // JS::Latin1Char
#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
#include "mozilla/Maybe.h" // Maybe
#include "mozilla/RefPtr.h" // RefPtr
#include "nsCOMPtr.h" // nsCOMPtr
#include "nsDebug.h" // NS_WARNING
#include "nsIPrincipal.h" // nsIPrincipal
#include "nsTArray.h" // nsTArray
#include "OriginAttributes.h"
#include "ExpandedPrincipal.h"
#include "SubsumedPrincipalJSONHandler.h"
#include "SharedJSONHandler.h"
namespace mozilla {
// JSON parse handler for an inner object for ExpandedPrincipal.
//
// # Legacy format
//
// inner object
// |
// -------------------------------------------------
// | |
// {"2": {"0": "eyIxIjp7IjAiOiJodHRwczovL2EuY29tLyJ9fQ=="}}
// | | |
// | ---------- Value
// | |
// PrincipalKind |
// |
// SerializableKeys
//
// The value is a CSV list of Base64 encoded prinipcals.
//
// # New format
//
// inner object
// |
// -------------------------------------------
// | |
// | Subsumed principals |
// | | |
// | ------------------------------------|
// | | ||
// {"2": {"0": [{"1": {"0": https://mozilla.com"}}]}}
// | | |
// -------------- Value
// |
// PrincipalKind
//
// Used by PrincipalJSONHandler.
class ExpandedPrincipalJSONHandler : public PrincipalJSONHandlerShared {
enum class State {
Init,
// After the inner object's '{'.
StartObject,
// After the property key for eSpecs.
SpecsKey,
// After the property key for eSuffix.
SuffixKey,
// After the subsumed principals array's '['.
StartArray,
// Subsumed principals array's item.
// Delegates to mSubsumedHandler until the subsumed object's '}'.
SubsumedPrincipal,
// After the property value for eSpecs or eSuffix,
// including after the subsumed principals array's ']'.
AfterPropertyValue,
// After the inner object's '}'.
EndObject,
Error,
};
public:
ExpandedPrincipalJSONHandler() = default;
virtual ~ExpandedPrincipalJSONHandler() = default;
virtual bool startObject() override;
using PrincipalJSONHandlerShared::propertyName;
virtual bool propertyName(const JS::Latin1Char* name, size_t length) override;
virtual bool endObject() override;
virtual bool startArray() override;
virtual bool endArray() override;
using PrincipalJSONHandlerShared::stringValue;
virtual bool stringValue(const JS::Latin1Char* str, size_t length) override;
bool HasAccepted() const { return mState == State::EndObject; }
protected:
virtual void SetErrorState() override { mState = State::Error; }
private:
bool ProcessSubsumedResult(bool aResult);
private:
State mState = State::Init;
nsTArray<nsCOMPtr<nsIPrincipal>> mAllowList;
OriginAttributes mAttrs;
Maybe<SubsumedPrincipalJSONHandler> mSubsumedHandler;
};
} // namespace mozilla
#endif // mozilla_ExpandedPrincipalJSONHandler_h

View File

@ -27,6 +27,9 @@
#include "pratom.h"
#include "nsIObjectInputStream.h"
#include "js/JSON.h"
#include "NullPrincipalJSONHandler.h"
using namespace mozilla;
NS_IMPL_CLASSINFO(NullPrincipal, nullptr, 0, NS_NULLPRINCIPAL_CID)
@ -249,43 +252,6 @@ nsresult NullPrincipal::WriteJSONInnerProperties(JSONWriter& aWriter) {
return NS_OK;
}
already_AddRefed<BasePrincipal> NullPrincipal::FromProperties(
nsTArray<NullPrincipal::KeyVal>& aFields) {
MOZ_ASSERT(aFields.Length() == eMax + 1, "Must have all the keys");
nsresult rv;
nsCOMPtr<nsIURI> uri;
OriginAttributes attrs;
// The odd structure here is to make the code to not compile
// if all the switch enum cases haven't been codified
for (const auto& field : aFields) {
switch (field.key) {
case NullPrincipal::eSpec:
if (!field.valueWasSerialized) {
MOZ_ASSERT(false,
"Null principals require a spec URI in serialized JSON");
return nullptr;
}
rv = NS_NewURI(getter_AddRefs(uri), field.value);
NS_ENSURE_SUCCESS(rv, nullptr);
break;
case NullPrincipal::eSuffix:
bool ok = attrs.PopulateFromSuffix(field.value);
if (!ok) {
return nullptr;
}
break;
}
}
if (!uri) {
MOZ_ASSERT(false, "No URI deserialized");
return nullptr;
}
return NullPrincipal::Create(attrs, uri);
}
NS_IMETHODIMP
NullPrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrincipal) {
*aPrincipal = nullptr;
@ -329,3 +295,107 @@ NullPrincipal::GetPrecursorPrincipal(nsIPrincipal** aPrincipal) {
contentPrincipal.forget(aPrincipal);
return NS_OK;
}
bool NullPrincipalJSONHandler::startObject() {
switch (mState) {
case State::Init:
mState = State::StartObject;
break;
default:
NS_WARNING("Unexpected object value");
mState = State::Error;
return false;
}
return true;
}
bool NullPrincipalJSONHandler::propertyName(const JS::Latin1Char* name,
size_t length) {
switch (mState) {
case State::StartObject:
case State::AfterPropertyValue: {
if (length != 1) {
NS_WARNING(
nsPrintfCString("Unexpected property name length: %zu", length)
.get());
mState = State::Error;
return false;
}
char key = char(name[0]);
switch (key) {
case NullPrincipal::SpecKey:
mState = State::SpecKey;
break;
case NullPrincipal::SuffixKey:
mState = State::SuffixKey;
break;
default:
NS_WARNING(
nsPrintfCString("Unexpected property name: '%c'", key).get());
mState = State::Error;
return false;
}
break;
}
default:
NS_WARNING("Unexpected property name");
mState = State::Error;
return false;
}
return true;
}
bool NullPrincipalJSONHandler::endObject() {
switch (mState) {
case State::AfterPropertyValue:
MOZ_ASSERT(mUri);
mPrincipal = NullPrincipal::Create(mAttrs, mUri);
MOZ_ASSERT(mPrincipal);
mState = State::EndObject;
break;
default:
NS_WARNING("Unexpected end of object");
mState = State::Error;
return false;
}
return true;
}
bool NullPrincipalJSONHandler::stringValue(const JS::Latin1Char* str,
size_t length) {
switch (mState) {
case State::SpecKey: {
nsDependentCSubstring spec(reinterpret_cast<const char*>(str), length);
nsresult rv = NS_NewURI(getter_AddRefs(mUri), spec);
if (NS_FAILED(rv)) {
mState = State::Error;
return false;
}
mState = State::AfterPropertyValue;
break;
}
case State::SuffixKey: {
nsDependentCSubstring attrs(reinterpret_cast<const char*>(str), length);
if (!mAttrs.PopulateFromSuffix(attrs)) {
mState = State::Error;
return false;
}
mState = State::AfterPropertyValue;
break;
}
default:
NS_WARNING("Unexpected string value");
mState = State::Error;
return false;
}
return true;
}

View File

@ -88,10 +88,11 @@ class NullPrincipal final : public BasePrincipal {
// Serializable keys are the valid enum fields the serialization supports
enum SerializableKeys : uint8_t { eSpec = 0, eSuffix, eMax = eSuffix };
typedef mozilla::BasePrincipal::KeyValT<SerializableKeys> KeyVal;
static already_AddRefed<BasePrincipal> FromProperties(
nsTArray<NullPrincipal::KeyVal>& aFields);
static constexpr char SpecKey = '0';
static_assert(eSpec == 0);
static constexpr char SuffixKey = '1';
static_assert(eSuffix == 1);
class Deserializer : public BasePrincipal::Deserializer {
public:

View File

@ -0,0 +1,91 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_NullPrincipalJSONHandler_h
#define mozilla_NullPrincipalJSONHandler_h
#include <stddef.h> // size_t
#include <stdint.h> // uint32_t
#include "js/TypeDecls.h" // JS::Latin1Char
#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
#include "mozilla/Assertions.h" // MOZ_ASSERT_UNREACHABLE
#include "mozilla/RefPtr.h" // RefPtr
#include "nsCOMPtr.h" // nsCOMPtr
#include "nsDebug.h" // NS_WARNING
#include "nsIURI.h" // nsIURI
#include "NullPrincipal.h"
#include "OriginAttributes.h"
#include "SharedJSONHandler.h"
namespace mozilla {
// JSON parse handler for an inner object for NullPrincipal.
// Used by PrincipalJSONHandler or SubsumedPrincipalJSONHandler.
class NullPrincipalJSONHandler : public PrincipalJSONHandlerShared {
enum class State {
Init,
// After the inner object's '{'.
StartObject,
// After the property key for eSpec.
SpecKey,
// After the property key for eSuffix.
SuffixKey,
// After the property value for eSpec or eSuffix.
AfterPropertyValue,
// After the inner object's '}'.
EndObject,
Error,
};
public:
NullPrincipalJSONHandler() = default;
virtual ~NullPrincipalJSONHandler() = default;
virtual bool startObject() override;
using PrincipalJSONHandlerShared::propertyName;
virtual bool propertyName(const JS::Latin1Char* name, size_t length) override;
virtual bool endObject() override;
virtual bool startArray() override {
NS_WARNING("Unexpected array value");
mState = State::Error;
return false;
}
virtual bool endArray() override {
NS_WARNING("Unexpected array value");
mState = State::Error;
return false;
}
using PrincipalJSONHandlerShared::stringValue;
virtual bool stringValue(const JS::Latin1Char* str, size_t length) override;
bool HasAccepted() const { return mState == State::EndObject; }
protected:
virtual void SetErrorState() override { mState = State::Error; }
private:
State mState = State::Init;
nsCOMPtr<nsIURI> mUri;
OriginAttributes mAttrs;
};
} // namespace mozilla
#endif // mozilla_NullPrincipalJSONHandler_h

116
caps/PrincipalJSONHandler.h Normal file
View File

@ -0,0 +1,116 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_PrincipalJSONHandler_h
#define mozilla_PrincipalJSONHandler_h
#include <stddef.h> // size_t
#include <stdint.h> // uint32_t
#include "js/JSON.h" // JS::JSONParseHandler
#include "js/TypeDecls.h" // JS::Latin1Char
#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
#include "mozilla/RefPtr.h" // RefPtr
#include "mozilla/Variant.h" // Variant
#include "nsDebug.h" // NS_WARNING
#include "nsPrintfCString.h" // nsPrintfCString
#include "BasePrincipal.h"
#include "ContentPrincipalJSONHandler.h"
#include "ExpandedPrincipalJSONHandler.h"
#include "NullPrincipalJSONHandler.h"
#include "SharedJSONHandler.h"
namespace mozilla {
class PrincipalJSONHandlerTypes {
public:
enum class State {
Init,
// After top-level object's '{'.
StartObject,
// After the PrincipalKind property key for SystemPrincipal.
SystemPrincipal_Key,
// After the SystemPrincipal's inner object's '{'.
SystemPrincipal_StartObject,
// After the SystemPrincipal's inner object's '}'.
SystemPrincipal_EndObject,
// After the PrincipalKind property key for NullPrincipal, ContentPrincipal,
// or ExpandedPrincipal, and also the entire inner object.
// Delegates to mInnerHandler until the inner object's '}'.
NullPrincipal_Inner,
ContentPrincipal_Inner,
ExpandedPrincipal_Inner,
// After top-level object's '}'.
EndObject,
Error,
};
using InnerHandlerT =
Maybe<Variant<NullPrincipalJSONHandler, ContentPrincipalJSONHandler,
ExpandedPrincipalJSONHandler>>;
static constexpr bool CanContainExpandedPrincipal = true;
};
// JSON parse handler for the top-level object for principal.
//
// inner object
// |
// ---------------------------------------------------------
// | |
// {"1": {"0": "https://mozilla.com", "2": "^privateBrowsingId=1"}}
// |
// |
// |
// PrincipalKind
//
// For inner object except for the system principal case, this delegates
// to NullPrincipalJSONHandler, ContentPrincipalJSONHandler, or
// ExpandedPrincipalJSONHandler.
//
//// Null principal:
// {"0":{"0":"moz-nullprincipal:{56cac540-864d-47e7-8e25-1614eab5155e}"}}
//
// Content principal:
// {"1":{"0":"https://mozilla.com"}} -> {"0":"https://mozilla.com"}
//
// Expanded principal:
// {"2":{"0":"<base64principal1>,<base64principal2>"}}
//
// System principal:
// {"3":{}}
class PrincipalJSONHandler
: public ContainerPrincipalJSONHandler<PrincipalJSONHandlerTypes> {
using State = PrincipalJSONHandlerTypes::State;
using InnerHandlerT = PrincipalJSONHandlerTypes::InnerHandlerT;
public:
PrincipalJSONHandler() = default;
virtual ~PrincipalJSONHandler() = default;
virtual void error(const char* msg, uint32_t line, uint32_t column) override {
NS_WARNING(
nsPrintfCString("JSON Error: %s at line %u column %u of the JSON data",
msg, line, column)
.get());
}
already_AddRefed<BasePrincipal> Get() { return mPrincipal.forget(); }
protected:
virtual void SetErrorState() override { mState = State::Error; }
};
} // namespace mozilla
#endif // mozilla_PrincipalJSONHandler_h

114
caps/SharedJSONHandler.h Normal file
View File

@ -0,0 +1,114 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_SharedJSONHandler_h
#define mozilla_SharedJSONHandler_h
#include "js/JSON.h" // JS::JSONParseHandler
#include "mozilla/RefPtr.h" // RefPtr
#include "BasePrincipal.h" // BasePrincipal
namespace mozilla {
// Base class of all principal JSON parse handlers.
class PrincipalJSONHandlerShared : public JS::JSONParseHandler {
public:
// Common handlers for inner objects.
// NOTE: propertyName and stringValue are partial overloads.
// Subclasses should put the following:
// * `using PrincipalJSONHandlerShared::propertyName`
// * `using PrincipalJSONHandlerShared::stringValue`
virtual bool propertyName(const char16_t* name, size_t length) override {
NS_WARNING("Principal JSON shouldn't use non-ASCII");
SetErrorState();
return false;
};
virtual bool stringValue(const char16_t* str, size_t length) override {
NS_WARNING("Principal JSON shouldn't use non-ASCII");
SetErrorState();
return true;
}
virtual bool numberValue(double d) override {
NS_WARNING("Unexpected number value");
SetErrorState();
return false;
}
virtual bool booleanValue(bool v) override {
NS_WARNING("Unexpected boolean value");
SetErrorState();
return false;
}
virtual bool nullValue() override {
NS_WARNING("Unexpected null value");
SetErrorState();
return false;
}
virtual void error(const char* msg, uint32_t line, uint32_t column) override {
// Unused.
}
protected:
// Set to the error state for the above handlers.
virtual void SetErrorState() = 0;
public:
RefPtr<BasePrincipal> mPrincipal;
};
// Base class shared between PrincipalJSONHandler and
// SubsumedPrincipalJSONHandler.
// This implements the common code for them, absorbing the difference about
// whether it can contain ExpandedPrincipal or not.
template <typename HandlerTypesT>
class ContainerPrincipalJSONHandler : public PrincipalJSONHandlerShared {
using State = typename HandlerTypesT::State;
using InnerHandlerT = typename HandlerTypesT::InnerHandlerT;
static constexpr bool CanContainExpandedPrincipal =
HandlerTypesT::CanContainExpandedPrincipal;
public:
// Common handlers.
virtual bool startObject() override;
using PrincipalJSONHandlerShared::propertyName;
virtual bool propertyName(const JS::Latin1Char* name, size_t length) override;
virtual bool endObject() override;
virtual bool startArray() override;
virtual bool endArray() override;
using PrincipalJSONHandlerShared::stringValue;
virtual bool stringValue(const JS::Latin1Char* str, size_t length) override;
private:
bool ProcessInnerResult(bool aResult);
template <class Func>
bool CallOnInner(Func&& aFunc) {
return mInnerHandler->match([&](auto& aInner) {
bool result = aFunc(aInner);
return ProcessInnerResult(result);
});
}
protected:
State mState = State::Init;
InnerHandlerT mInnerHandler;
};
} // namespace mozilla
#endif // mozilla_SharedJSONHandler_h

View File

@ -0,0 +1,96 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_SubsumedPrincipalJSONHandler_h
#define mozilla_SubsumedPrincipalJSONHandler_h
#include <stddef.h> // size_t
#include <stdint.h> // uint32_t
#include "js/TypeDecls.h" // JS::Latin1Char
#include "mozilla/AlreadyAddRefed.h" // already_AddRefed
#include "mozilla/Variant.h" // Variant
#include "mozilla/RefPtr.h" // RefPtr
#include "nsDebug.h" // NS_WARNING
#include "BasePrincipal.h"
#include "ContentPrincipalJSONHandler.h"
#include "NullPrincipalJSONHandler.h"
#include "SharedJSONHandler.h"
namespace mozilla {
class SubsumedPrincipalJSONHandlerTypes {
public:
enum class State {
Init,
// After the subsumed principal object's '{'.
StartObject,
// After the PrincipalKind property key for SystemPrincipal.
SystemPrincipal_Key,
// After the SystemPrincipal's inner object's '{'.
SystemPrincipal_StartObject,
// After the SystemPrincipal's inner object's '}'.
SystemPrincipal_EndObject,
// After the PrincipalKind property key for NullPrincipal or
// ContentPrincipal, and also the entire inner object.
// Delegates to mInnerHandler until the inner object's '}'.
//
// Unlike PrincipalJSONHandler, subsumed principal doesn't contain
// ExpandedPrincipal.
NullPrincipal_Inner,
ContentPrincipal_Inner,
// After the subsumed principal object's '}'.
EndObject,
Error,
};
using InnerHandlerT =
Maybe<Variant<NullPrincipalJSONHandler, ContentPrincipalJSONHandler>>;
static constexpr bool CanContainExpandedPrincipal = false;
};
// JSON parse handler for subsumed principal object inside ExpandedPrincipal's
// new format.
//
// Subsumed principal object
// |
// ----------------------------------
// | |
// {"2": {"0": [{"1": {"0": https://mozilla.com"}}]}}
// | | |
// | ---------------------------
// | |
// | inner object
// PrincipalKind
//
// For inner object except for the system principal case, this delegates
// to NullPrincipalJSONHandler or ContentPrincipalJSONHandler.
class SubsumedPrincipalJSONHandler
: public ContainerPrincipalJSONHandler<SubsumedPrincipalJSONHandlerTypes> {
using State = SubsumedPrincipalJSONHandlerTypes::State;
using InnerHandlerT = SubsumedPrincipalJSONHandlerTypes::InnerHandlerT;
public:
SubsumedPrincipalJSONHandler() = default;
virtual ~SubsumedPrincipalJSONHandler() = default;
bool HasAccepted() const { return mState == State::EndObject; }
protected:
virtual void SetErrorState() override { mState = State::Error; }
};
} // namespace mozilla
#endif // mozilla_SubsumedPrincipalJSONHandler_h

View File

@ -24,10 +24,6 @@
class nsScriptSecurityManager;
namespace Json {
class Value;
}
namespace mozilla {
class SystemPrincipal final : public BasePrincipal, public nsISerializable {

View File

@ -17,7 +17,7 @@ using mozilla::SystemPrincipal;
// None of these tests work in debug due to assert guards
#ifndef MOZ_DEBUG
// calling toJson() twice with the same string arg
// calling toJSON() twice with the same string arg
// (ensure that we truncate correctly where needed)
TEST(PrincipalSerialization, ReusedJSONArgument)
{
@ -46,28 +46,6 @@ TEST(PrincipalSerialization, ReusedJSONArgument)
ASSERT_TRUE(JSON.EqualsLiteral("{\"1\":{\"0\":\"https://example.com/\"}}"));
}
// Assure that calling FromProperties() with an empty array list always returns
// a nullptr The exception here is SystemPrincipal which doesn't have fields but
// it also doesn't implement FromProperties These are overly cautious checks
// that we don't try to create a principal in reality FromProperties is only
// called with a populated array.
TEST(PrincipalSerialization, FromPropertiesEmpty)
{
nsTArray<ContentPrincipal::KeyVal> resContent;
nsCOMPtr<nsIPrincipal> contentPrincipal =
ContentPrincipal::FromProperties(resContent);
ASSERT_EQ(nullptr, contentPrincipal);
nsTArray<ExpandedPrincipal::KeyVal> resExpanded;
nsCOMPtr<nsIPrincipal> expandedPrincipal =
ExpandedPrincipal::FromProperties(resExpanded);
ASSERT_EQ(nullptr, expandedPrincipal);
nsTArray<NullPrincipal::KeyVal> resNull;
nsCOMPtr<nsIPrincipal> nullprincipal = NullPrincipal::FromProperties(resNull);
ASSERT_EQ(nullptr, nullprincipal);
}
// Double check that if we have two valid principals in a serialized JSON that
// nullptr is returned
TEST(PrincipalSerialization, TwoKeys)