This is a library that provides a compatibility layer between AdGuard filtering rules and Safari content blocking rules.
The main purpose of this project is converting AdGuard rules into the format that Safari can understand.
Here's a simple example of how it works.
-
AdGuard rule:
||example.org^$third-party
-
Safari rule that does the same:
{ "action": { "type": "block" }, "trigger": { "load-type": [ "third-party" ], "url-filter": "^[htpsw]+:\\\/\\\/([a-z0-9-]+\\.)?test\\.com([\\\/:&\\?].*)?$" } }
Note that not every rule can be supported natively, so there's also a concept of "advanced rules," the rules that have to be interpreted by a separate extension, either a WebExtension or a Safari App Extension.
You can check out the demo project to see how to use the library. It showcases all the features of the library.
The first step is to use an instance of ContentBlockerConverter to convert AdGuard rules into the rules that Safari understands.
let result: ConversionResult = ContentBlockerConverter().convertArray(
rules: lines,
safariVersion: SafariVersion.autodetect(),
advancedBlocking: true,
maxJsonSizeBytes: nil,
progress: nil
)
Tip
In order to keep parsing fast, we heavily rely on UTF8View
. Users need to
avoid extra conversion to UTF-8. Please make sure that the filter's content
is stored with this encoding. You can ensure this by calling
makeContiguousUTF8
before splitting the filter
content into rules to pass them to the converter. If the String
is already
contiguous, this call does not cost anything.
Once the conversion is finished, you'll have a ConversionResult object that contains two important fields:
safariRulesJSON
- JSON with Safari content blocking rules. This JSON should be interpreted by a content blocker.advancedRulesText
- AdGuard rules that need to be interpreted by web extension (or app extension). Please refer to the extension's README for details.
Important
Please read the extension's README for the explanation on how to use the advanced rules.
In addition to that you can use ContentBlockerConverterVersion
class to get
the version of the library and its components in your app.
let version = ContentBlockerConverterVersion.library
let scriptletsVersion = ContentBlockerConverterVersion.scriptlets
let extendedCSSVersion = ContentBlockerConverterVersion.extendedCSS
Converter can be built as a command-line tool with the following interface:
OVERVIEW: Tool for converting rules to JSON or building the FilterEngine binary.
USAGE: ConverterTool <subcommand>
OPTIONS:
-h, --help Show help information.
SUBCOMMANDS:
convert (default) Convert AdGuard rules to Safari content blocking JSON
and advanced rules for a web extension.
buildengine Build the FilterEngine binary.
See 'ConverterTool help <subcommand>' for detailed help.
Safari Converter aims to support AdGuard filtering rules syntax as much as possible, but still there are limitations and shortcomings that are hard to overcome.
Safari Converter supports a substantial subset of basic rules and certainly supports the most important types of those rules.
-
Regular expression rules are limited to the subset of regex that is supported by Safari.
-
$domain
- domain modifier is supported with several limitations.- It's impossible to mix allowed and disallowed domains (like
$domain=example.org|~sub.example.org
). Please upvote the feature request to WebKit to lift this limitation. - "Any TLD" (i.e.
domain.*
) is not fully supported. In the current implementation the converter just replaces.*
with top 100 popular TLDs. This implementation will be improved in the future. - Using regular expressions in
$domain
is not supported, but it also will be improved in the future.
- It's impossible to mix allowed and disallowed domains (like
-
$denyallow
- this modifier is supported via converting$denyallow
rule to a set of rules (one blocking rule + several unblocking rules).Due to that limitation
$denyallow
is only allowed when the rule also has$domain
modifier.- Generic rule
*$denyallow=x.com,image,domain=a.com
will be converted to:
*$image,domain=a.com @@||x.com$image,domain=a.com
- Rule
/banner.png$image,denyallow=test1.com|test2.com,domain=example.org
will be converted to:
/banner.png$image,domain=example.org @@||test1.com/banner.png$image,domain=example.org @@||test1.com/*/banner.png$image,domain=example.org @@||test2.com/banner.png$image,domain=example.org @@||test2.com/*/banner.png$image,domain=example.org
- Rule without
$domain
is not supported:$denyallow=a.com|b.com
.
- Generic rule
-
$popup
- popup rules are supported, but they're basically the same as$document
-blocking rules and will not attempt to close the tab. -
Exception rules (
@@
) disable cosmetic filtering on matching domains.Exception rules in Safari rely on the rule type
ignore-previous-rules
so to make it work we have to order the rules in a specific order. Exception rules without modifiers are placed at the end of the list and therefore they disable not just URL blocking, but cosmetic rules as well.This limitation may be lifted if #70 is implemented.
-
$urlblock
,$genericblock
is basically the same as$document
, i.e., it disables all kinds of filtering on websites.These limitations may be lifted when #69 and #71 are implemented.
-
$content
makes no sense in the case of Safari since HTML filtering rules are not supported so it's there for compatibility purposes only. Rules with$content
modifier are limited todocument
resource type. -
$specifichide
is implemented by scanning existing element hiding rules and removing the target domain from theirif-domain
array.$specifichide
rules MUST target a domain, i.e. be like this:||example.org^$specifichide
. Rules with more specific patterns will be discarded, i.e.||example.org/path$specifichide
will not be supported.$specifichide
rules only cover rules that target the same domain as the rule itself, subdomains are ignored. I.e. the rule@@||example.org^$specifichide
will disableexample.org##.banner
, but will ignoresub.example.org##.banner
. This limitation may be lifted if #72 is implemented.
-
urlblock
,genericblock
,generichide
,elemhide
,specifichide
, andjsinject
modifiers can be used only as a single modifier in a rule. This limitation may be lifted in the future: #73. -
$websocket
(fully supported starting with Safari 15). -
$ping
(fully supported starting with Safari 14). -
$jsinject
- rules with this modifier are converted to advanced blocking rules. Currently,$jsinject
modifier can be used only as a single modifier in a rule. This limitation may be lifted in the future: #73.
$app
$header
$method
$strict-first-party
(to be supported in the future: #64)$strict-third-party
(to be supported in the future: #65)$to
(to be supported in the future: #60)$extension
$stealth
$cookie
(partial support in the future: #54)$csp
$hls
$inline-script
$inline-font
$jsonprune
$xmlprune
$network
$permissions
$redirect
$redirect-rule
$referrerpolicy
$removeheader
$removeparam
$replace
$urltransform
Safari Converter supports most of the cosmetic rules although only element hiding rules with basic CSS selectors are supported natively via Safari Content Blocking, everything else needs to be interpreted by an additional extension.
-
Specifying domains is subject to the same limitations as the
$domain
modifier of basic rules. -
Non-basic rules modifiers are supported with some limitations:
$domain
- the same limitations as everywhere else.$path
- supported, but if you use regular expressions, they will be limited to the subset of regex that is supported by Safari.$url
- to be supported in the future: #68
Safari Converter fully supports both script rules and scriptlet rules. However, these rules can only be interpreted by a separate extension.
Warning
For scriptlet rules it is very important to run them as soon as possible when the page is loaded. The reason for that is that it's important to run earlier than the page scripts do. Unfortunately, with Safari there will always be a slight delay that can decrease the quality of blocking.
HTML filtering rules are not supported and will not be supported in the future. Unfortunately, Safari does not provide necessary technical capabilities to implement them.
Please note, that the library is published under GPLv3.
Please refer to DEVELOPMENT.md for details.
- Choose the new version using Semantic Versioning.
- Update the CHANGELOG.md and add new version information.
- Run
VERSION=${version} make codegen
to update the version of the extension, and to generateContentBlockerConverterVersion
. - Make
Bump version to ${version}
commit. - Run
Converter - build for release
plan in Bamboo and overriderelease.version
variable. - This plan will add a new tag in
v*.*.*
format. - Run the linked
Converter - deploy
plan:- This plan will publish to NPM the new version of the library @adguard/safari-extension
- It will also publish a new Github release to this repo.