Skip to content

Commit

Permalink
Parse web extension manifest for declarativeNetRequest rulesets
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=264836
rdar://118415584

Reviewed by Timothy Hatcher.

This patch hooks up basic manifest parsing for declarativeNetRequest rule sets.

* Source/WebCore/en.lproj/Localizable.strings:
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.h:
* Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.mm:
(-[_WKWebExtension hasDeclarativeNetRequestRules]): Mostly added for testing at this point. Returns if there were any correctly parsed rulesets.
* Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionCocoa.mm:
(WebKit::WebExtension::errors): Call parseDeclarativeNetRequestRulesetDictionary.
(WebKit::WebExtension::declarativeNetRequestRulesets): Call populateDeclarativeNetRequestPropertiesIfNeeded and return the vector of rulesets.
(WebKit::WebExtension::parseDeclarativeNetRequestRulesetDictionary): Takes in a dictionary and returns an optional DeclarativeNetRequestRulesetData. This method
does argument type checking and makes sure the string arguments aren't nil or empty.
(WebKit::WebExtension::populateDeclarativeNetRequestPropertiesIfNeeded): Read the manifest and turn the JSON into a vector of DeclarativeNetRequestRulesetData. This
PR makes sure the extension doesn't specify too many rulesets, too many enabled rulesets, or multiple rulesets with the same id.
* Source/WebKit/UIProcess/Extensions/WebExtension.h:
* Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebExtension.mm:
(TestWebKitAPI::TEST): Add some tests for the manifest parsing.

Canonical link: https://commits.webkit.org/270751@main
  • Loading branch information
b-weinstein committed Nov 15, 2023
1 parent e2b769d commit 1093d60
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 4 deletions.
24 changes: 21 additions & 3 deletions Source/WebCore/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,12 @@
/* Display name for easy reader (i.e. 3rd-grade level) text tracks. */
"Easy Reader (text track)" = "Easy Reader";

/* _WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for empty JSON path */
"Empty `declarative_net_request` JSON path." = "Empty `declarative_net_request` JSON path.";

/* _WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for empty ruleset id */
"Empty `declarative_net_request` ruleset id." = "Empty `declarative_net_request` ruleset id.";

/* WKWebExtensionErrorInvalidManifestEntry description for background */
"Empty or invalid `background` manifest entry." = "Empty or invalid `background` manifest entry.";

Expand Down Expand Up @@ -532,6 +538,12 @@
/* Custom protocol synchronous load failure description */
"Error handling synchronous load with custom protocol" = "Error handling synchronous load with custom protocol";

/* _WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for too many rulesets */
"Exceeded maximum number of `declarative_net_request` rulesets. Ignoring extra rulesets." = "Exceeded maximum number of `declarative_net_request` rulesets. Ignoring extra rulesets.";

/* _WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for too many enabled static rulesets */
"Exceeded maxmimum number of enabled `declarative_net_request` static rulesets. The first %lu will be applied, the remaining will be ignored." = "Exceeded maxmimum number of enabled `declarative_net_request` static rulesets. The first %lu will be applied, the remaining will be ignored.";

/* Button for exiting full screen when in full screen media playback */
"Exit Full Screen" = "Exit Full Screen";

Expand Down Expand Up @@ -781,6 +793,9 @@
/* WKWebExtensionErrorInvalidContentScripts description for unknown 'run_at' value */
"Manifest `content_scripts` entry has unknown `run_at` value." = "Manifest `content_scripts` entry has unknown `run_at` value.";

/* _WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for missing declarativeNetRequest permission */
"Manifest has no `declarativeNetRequest` permission." = "Manifest has no `declarativeNetRequest` permission.";

/* Validation message for input form controls requiring a constrained value according to pattern */
"Match the requested format" = "Match the requested format";

Expand Down Expand Up @@ -973,6 +988,9 @@
/* Previous Page context menu item */
"Previous Page" = "Previous Page";

/* Title for the webarea while the accessibility tree is being built. */
"Processing page" = "Processing page";

/* Undo action name */
"Raise Baseline (Undo action name)" = "Raise Baseline";

Expand Down Expand Up @@ -1444,6 +1462,9 @@
/* Zoom Out context menu item */
"Zoom Out" = "Zoom Out";

/* _WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for duplicate ruleset id */
"`declarative_net_request` ruleset with id \"%@\" is invalid. Ruleset id must be unique." = "`declarative_net_request` ruleset with id \"%@\" is invalid. Ruleset id must be unique.";

/* Verb stating the action that will occur when a text field is selected, as used by accessibility */
"activate" = "activate";

Expand Down Expand Up @@ -1786,9 +1807,6 @@
/* accessibility label for a date field year input. */
"year" = "year";

/* Accessibility message to indicate that a page is being processed to be accessed. */
"Processing page" = "Processing page";

/* Message for requesting access to the device motion and orientation */
"“%@” Would Like to Access Motion and Orientation" = "“%@” Would Like to Access Motion and Orientation";

Expand Down
3 changes: 3 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ WK_CLASS_AVAILABLE(macos(13.3), ios(16.4))
*/
@property (nonatomic, readonly) BOOL hasCommands;

/*! @abstract A boolean value indicating whether the extension includes rules used for content modification or blocking. */
@property (nonatomic, readonly) BOOL hasContentModificationRules;

@end

NS_ASSUME_NONNULL_END
10 changes: 10 additions & 0 deletions Source/WebKit/UIProcess/API/Cocoa/_WKWebExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ - (BOOL)hasCommands
return _webExtension->hasCommands();
}

- (BOOL)hasContentModificationRules
{
return _webExtension->hasContentModificationRules();
}

- (BOOL)_backgroundContentIsServiceWorker
{
return _webExtension->backgroundContentIsServiceWorker();
Expand Down Expand Up @@ -454,6 +459,11 @@ - (BOOL)hasCommands
return NO;
}

- (BOOL)hasContentModificationRules
{
return NO;
}

- (BOOL)_backgroundContentIsServiceWorker
{
return NO;
Expand Down
130 changes: 129 additions & 1 deletion Source/WebKit/UIProcess/Extensions/Cocoa/WebExtensionCocoa.mm
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#import "CocoaHelpers.h"
#import "FoundationSPI.h"
#import "Logging.h"
#import "WebExtensionUtilities.h"
#import "_WKWebExtensionInternal.h"
#import "_WKWebExtensionLocalization.h"
#import "_WKWebExtensionPermission.h"
Expand Down Expand Up @@ -132,8 +133,17 @@
static NSString * const webAccessibleResourcesResourcesManifestKey = @"resources";
static NSString * const webAccessibleResourcesMatchesManifestKey = @"matches";

static NSString * const declarativeNetRequestManifestKey = @"declarative_net_request";
static NSString * const declarativeNetRequestRulesManifestKey = @"rule_resources";
static NSString * const declarativeNetRequestRulesetIDManifestKey = @"id";
static NSString * const declarativeNetRequestRuleEnabledManifestKey = @"enabled";
static NSString * const declarativeNetRequestRulePathManifestKey = @"path";

static const size_t maximumNumberOfShortcutCommands = 4;

static const size_t maximumNumberOfDeclarativeNetRequestStaticRulesets = 100;
static const size_t maximumNumberOfDeclarativeNetRequestEnabledStaticRulesets = 50;

WebExtension::WebExtension(NSBundle *appExtensionBundle, NSError **outError)
: m_bundle(appExtensionBundle)
, m_resourceBaseURL(appExtensionBundle.resourceURL.URLByStandardizingPath.absoluteURL)
Expand Down Expand Up @@ -771,7 +781,7 @@ static _WKWebExtensionError toAPI(WebExtension::Error error)

NSArray *WebExtension::errors()
{
// FIXME: <https://webkit.org/b/246493> Include runtime and declarativeNetRequest errors.
// FIXME: <https://webkit.org/b/246493> Include runtime errors.

populateDisplayStringsIfNeeded();
populateActionPropertiesIfNeeded();
Expand All @@ -782,6 +792,7 @@ static _WKWebExtensionError toAPI(WebExtension::Error error)
populateContentSecurityPolicyStringsIfNeeded();
populateWebAccessibleResourcesIfNeeded();
populateCommandsIfNeeded();
populateDeclarativeNetRequestPropertiesIfNeeded();

return [m_errors copy] ?: @[ ];
}
Expand Down Expand Up @@ -1601,6 +1612,123 @@ static bool parseCommandShortcut(const String& shortcut, OptionSet<ModifierFlags
return !m_staticInjectedContents.isEmpty();
}

std::optional<WebExtension::DeclarativeNetRequestRulesetData> WebExtension::parseDeclarativeNetRequestRulesetDictionary(NSDictionary *rulesetDictionary, NSError **error)
{
NSArray *requiredKeysInRulesetDictionary = @[
declarativeNetRequestRulesetIDManifestKey,
declarativeNetRequestRuleEnabledManifestKey,
declarativeNetRequestRulePathManifestKey,
];

NSDictionary *keyToExpectedValueTypeInRulesetDictionary = @{
declarativeNetRequestRulesetIDManifestKey: NSString.class,
declarativeNetRequestRuleEnabledManifestKey: @YES.class,
declarativeNetRequestRulePathManifestKey: NSString.class,
};

NSString *exceptionString;
bool isRulesetDictionaryValid = validateDictionary(rulesetDictionary, nil, requiredKeysInRulesetDictionary, keyToExpectedValueTypeInRulesetDictionary, &exceptionString);
if (!isRulesetDictionaryValid) {
*error = createError(WebExtension::Error::InvalidDeclarativeNetRequest, exceptionString);
return std::nullopt;
}

NSString *rulesetID = objectForKey<NSString>(rulesetDictionary, declarativeNetRequestRulesetIDManifestKey);
if (!rulesetID.length) {
*error = createError(WebExtension::Error::InvalidDeclarativeNetRequest, WEB_UI_STRING("Empty `declarative_net_request` ruleset id.", "_WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for empty ruleset id"));
return std::nullopt;
}

NSString *jsonPath = objectForKey<NSString>(rulesetDictionary, declarativeNetRequestRulePathManifestKey);
if (!jsonPath.length) {
*error = createError(WebExtension::Error::InvalidDeclarativeNetRequest, WEB_UI_STRING("Empty `declarative_net_request` JSON path.", "_WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for empty JSON path"));
return std::nullopt;

}

DeclarativeNetRequestRulesetData rulesetData = {
rulesetID,
(bool)objectForKey<NSNumber>(rulesetDictionary, declarativeNetRequestRuleEnabledManifestKey).boolValue,
jsonPath
};

return std::optional { WTFMove(rulesetData) };
}

void WebExtension::populateDeclarativeNetRequestPropertiesIfNeeded()
{
if (!manifestParsedSuccessfully())
return;

if (m_parsedManifestDeclarativeNetRequestRulesets)
return;

m_parsedManifestDeclarativeNetRequestRulesets = true;

// Documentation: https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/manifest.json/declarative_net_request

if (!supportedPermissions().contains(_WKWebExtensionPermissionDeclarativeNetRequest) && !supportedPermissions().contains(_WKWebExtensionPermissionDeclarativeNetRequestWithHostAccess)) {
recordError(createError(Error::InvalidDeclarativeNetRequest, WEB_UI_STRING("Manifest has no `declarativeNetRequest` permission.", "_WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for missing declarativeNetRequest permission")));
return;
}

auto *declarativeNetRequestManifestDictionary = objectForKey<NSDictionary>(m_manifest, declarativeNetRequestManifestKey);
if (!declarativeNetRequestManifestDictionary) {
if ([m_manifest objectForKey:declarativeNetRequestManifestKey])
recordError(createError(Error::InvalidDeclarativeNetRequest));
return;
}

NSArray<NSDictionary *> *declarativeNetRequestRulesets = objectForKey<NSArray>(declarativeNetRequestManifestDictionary, declarativeNetRequestRulesManifestKey, true, NSDictionary.class);
if (!declarativeNetRequestRulesets) {
if ([m_manifest objectForKey:declarativeNetRequestManifestKey])
recordError(createError(Error::InvalidDeclarativeNetRequest));
return;
}

if (declarativeNetRequestRulesets.count > maximumNumberOfDeclarativeNetRequestStaticRulesets)
recordError(createError(Error::InvalidDeclarativeNetRequest, WEB_UI_STRING("Exceeded maximum number of `declarative_net_request` rulesets. Ignoring extra rulesets.", "_WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for too many rulesets")));

NSUInteger rulesetCount = 0;
NSUInteger enabledRulesetCount = 0;
bool recordedTooManyRulesetsManifestError = false;
HashSet<String> seenRulesetIDs;
for (NSDictionary *rulesetDictionary in declarativeNetRequestRulesets) {
if (rulesetCount >= maximumNumberOfDeclarativeNetRequestStaticRulesets)
continue;

NSError *error;
auto optionalRuleset = parseDeclarativeNetRequestRulesetDictionary(rulesetDictionary, &error);
if (!optionalRuleset) {
recordError(createError(Error::InvalidDeclarativeNetRequest, nil, error));
continue;
}

auto ruleset = optionalRuleset.value();
if (seenRulesetIDs.contains(ruleset.rulesetID)) {
recordError(createError(Error::InvalidDeclarativeNetRequest, WEB_UI_FORMAT_STRING("`declarative_net_request` ruleset with id \"%@\" is invalid. Ruleset id must be unique.", "_WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for duplicate ruleset id", (NSString *)ruleset.rulesetID)));
continue;
}

if (ruleset.enabled && ++enabledRulesetCount > maximumNumberOfDeclarativeNetRequestEnabledStaticRulesets && !recordedTooManyRulesetsManifestError) {
recordError(createError(Error::InvalidDeclarativeNetRequest, WEB_UI_FORMAT_STRING("Exceeded maxmimum number of enabled `declarative_net_request` static rulesets. The first %lu will be applied, the remaining will be ignored.", "_WKWebExtensionErrorInvalidDeclarativeNetRequestEntry description for too many enabled static rulesets", maximumNumberOfDeclarativeNetRequestEnabledStaticRulesets)));
recordedTooManyRulesetsManifestError = true;
continue;
}

seenRulesetIDs.add(ruleset.rulesetID);
++rulesetCount;

m_declarativeNetRequestRulesets.append(ruleset);
}
}

const WebExtension::DeclarativeNetRequestRulesetVector& WebExtension::declarativeNetRequestRulesets()
{
populateDeclarativeNetRequestPropertiesIfNeeded();
return m_declarativeNetRequestRulesets;
}

NSArray *WebExtension::InjectedContentData::expandedIncludeMatchPatternStrings() const
{
NSMutableArray<NSString *> *result = [NSMutableArray array];
Expand Down
15 changes: 15 additions & 0 deletions Source/WebKit/UIProcess/Extensions/WebExtension.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,16 @@ class WebExtension : public API::ObjectImpl<API::Object::Type::WebExtension>, pu
Vector<String> resourcePathPatterns;
};

struct DeclarativeNetRequestRulesetData {
String rulesetID;
bool enabled;
String jsonPath;
};

using CommandsVector = Vector<CommandData>;
using InjectedContentVector = Vector<InjectedContentData>;
using WebAccessibleResourcesVector = Vector<WebAccessibleResourceData>;
using DeclarativeNetRequestRulesetVector = Vector<DeclarativeNetRequestRulesetData>;

static const PermissionsSet& supportedPermissions();

Expand Down Expand Up @@ -235,6 +242,9 @@ class WebExtension : public API::ObjectImpl<API::Object::Type::WebExtension>, pu
const CommandsVector& commands();
bool hasCommands();

const DeclarativeNetRequestRulesetVector& declarativeNetRequestRulesets();
bool hasContentModificationRules() { return !declarativeNetRequestRulesets().isEmpty(); }

const InjectedContentVector& staticInjectedContents();
bool hasStaticInjectedContentForURL(NSURL *);
bool hasStaticInjectedContent();
Expand Down Expand Up @@ -279,10 +289,14 @@ class WebExtension : public API::ObjectImpl<API::Object::Type::WebExtension>, pu
void populateContentSecurityPolicyStringsIfNeeded();
void populateWebAccessibleResourcesIfNeeded();
void populateCommandsIfNeeded();
void populateDeclarativeNetRequestPropertiesIfNeeded();

std::optional<WebExtension::DeclarativeNetRequestRulesetData> parseDeclarativeNetRequestRulesetDictionary(NSDictionary *, NSError **);

InjectedContentVector m_staticInjectedContents;
WebAccessibleResourcesVector m_webAccessibleResources;
CommandsVector m_commands;
DeclarativeNetRequestRulesetVector m_declarativeNetRequestRulesets;

MatchPatternSet m_permissionMatchPatterns;
MatchPatternSet m_optionalPermissionMatchPatterns;
Expand Down Expand Up @@ -339,6 +353,7 @@ class WebExtension : public API::ObjectImpl<API::Object::Type::WebExtension>, pu
bool m_parsedManifestPageProperties : 1 { false };
bool m_parsedManifestWebAccessibleResources : 1 { false };
bool m_parsedManifestCommands : 1 { false };
bool m_parsedManifestDeclarativeNetRequestRulesets : 1 { false };
};

#ifdef __OBJC__
Expand Down
Loading

0 comments on commit 1093d60

Please sign in to comment.