├── .build
└── workspace-state.json
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── bug-report-for-webshield.md
│ └── feature-request-for-webshield.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CodingStyle.md
├── ISSUES.md
├── LICENSE
├── README.md
├── Shared
└── App+ContentBlocker+Extension.entitlements
├── Tests
├── WebShieldAppTests
│ └── AppTests.swift
└── WebShieldAppUITests
│ ├── AppUITests.swift
│ └── AppUITestsLaunchTests.swift
├── WebShield.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── xcuserdata
│ │ └── a.xcuserdatad
│ │ ├── IDEFindNavigatorScopes.plist
│ │ └── WorkspaceSettings.xcsettings
├── xcshareddata
│ └── xcschemes
│ │ ├── AppTests.xcscheme
│ │ ├── WebShield Ads.xcscheme
│ │ ├── WebShield Advanced.xcscheme
│ │ ├── WebShield Annoyances.xcscheme
│ │ ├── WebShield Cookies.xcscheme
│ │ ├── WebShield Custom.xcscheme
│ │ ├── WebShield Experimental.xcscheme
│ │ ├── WebShield Multipurpose.xcscheme
│ │ ├── WebShield Privacy.xcscheme
│ │ ├── WebShield Regional.xcscheme
│ │ ├── WebShield Security.xcscheme
│ │ ├── WebShield Social.xcscheme
│ │ └── WebShield.xcscheme
└── xcuserdata
│ └── a.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── WebShieldAds
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldAdvanced
├── Info.plist
├── Resources
│ ├── _locales
│ │ └── en
│ │ │ └── messages.json
│ ├── images
│ │ ├── icon-128.png
│ │ ├── icon-256.png
│ │ ├── icon-48.png
│ │ ├── icon-512.png
│ │ ├── icon-64.png
│ │ ├── icon-96.png
│ │ └── toolbar-icon.svg
│ ├── manifest.json
│ ├── popup
│ │ ├── popup.css
│ │ ├── popup.html
│ │ └── popup.js
│ └── src
│ │ ├── background.js
│ │ ├── content.js
│ │ ├── extendedCss
│ │ └── extended-css.js
│ │ └── scriptlets
│ │ └── scriptlets.js
└── Source
│ ├── ChunkFileReader.swift
│ ├── ContentBlockerEngineWrapper.swift
│ ├── NotificationManager.swift
│ └── SafariWebExtensionHandler.swift
├── WebShieldAnnoyances
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldApp
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── 1024-mac.png
│ │ ├── 1024.png
│ │ ├── 128-mac.png
│ │ ├── 16-mac.png
│ │ ├── 256-mac 1.png
│ │ ├── 256-mac.png
│ │ ├── 32-mac 1.png
│ │ ├── 32-mac.png
│ │ ├── 512-mac 1.png
│ │ ├── 512-mac.png
│ │ ├── 64-mac.png
│ │ ├── Contents.json
│ │ ├── WebShield Final (No Text) 1.png
│ │ └── WebShield Final (No Text).png
│ ├── AppIcon.solidimagestack
│ │ ├── Back.solidimagestacklayer
│ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── WebShield Final Layer 1 (Background).png
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Front.solidimagestacklayer
│ │ │ ├── Content.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── WebShield Final Layer 2 (Shield).png
│ │ │ └── Contents.json
│ │ └── Middle.solidimagestacklayer
│ │ │ ├── Content.imageset
│ │ │ ├── Contents.json
│ │ │ └── WebShield Final Layer 1 (Background).png
│ │ │ └── Contents.json
│ └── Contents.json
├── Info.plist
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Resources
│ ├── LaunchScreen.storyboard
│ ├── iOS
│ │ └── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── WebShield Final (No Text)-1024.png
│ │ │ ├── WebShield Final (No Text)-20.png
│ │ │ ├── WebShield Final (No Text)-20@2x.png
│ │ │ ├── WebShield Final (No Text)-20@3x.png
│ │ │ ├── WebShield Final (No Text)-29.png
│ │ │ ├── WebShield Final (No Text)-29@2x.png
│ │ │ ├── WebShield Final (No Text)-29@3x.png
│ │ │ ├── WebShield Final (No Text)-40.png
│ │ │ ├── WebShield Final (No Text)-40@2x.png
│ │ │ ├── WebShield Final (No Text)-40@3x.png
│ │ │ ├── WebShield Final (No Text)-60@2x.png
│ │ │ ├── WebShield Final (No Text)-60@3x.png
│ │ │ ├── WebShield Final (No Text)-76.png
│ │ │ ├── WebShield Final (No Text)-76@2x.png
│ │ │ └── WebShield Final (No Text)-83.5@2x.png
│ └── macOS
│ │ └── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── WebShield Final (No Text)-1024.png
│ │ ├── WebShield Final (No Text)-128.png
│ │ ├── WebShield Final (No Text)-16.png
│ │ ├── WebShield Final (No Text)-256.png
│ │ ├── WebShield Final (No Text)-32.png
│ │ ├── WebShield Final (No Text)-512.png
│ │ └── WebShield Final (No Text)-64.png
├── Source
│ ├── App
│ │ ├── AppSettings.swift
│ │ ├── GroupContainerURL.swift
│ │ ├── Identifiers.swift
│ │ └── WebShieldApp.swift
│ ├── Extensions
│ │ ├── ColorExtension.swift
│ │ └── ModelContextExtension.swift
│ ├── Models
│ │ ├── FilterList.swift
│ │ ├── FilterListCategory.swift
│ │ ├── FilterListData.swift
│ │ ├── FilterListError.swift
│ │ ├── FilterListSection.swift
│ │ ├── ParsedMetadata.swift
│ │ ├── ProcessedConversionResult.swift
│ │ ├── RefreshError.swift
│ │ ├── RefreshState.swift
│ │ ├── TotalStats.swift
│ │ └── TrustedSite.swift
│ ├── Providers
│ │ └── FilterListProvider.swift
│ ├── Services
│ │ ├── ContentBlockerState.swift
│ │ ├── DataManager.swift
│ │ ├── FilterListProcessor.swift
│ │ ├── WebExtensionState.swift
│ │ └── WebShieldLogger.swift
│ ├── ViewModels
│ │ ├── ExtensionCheckViewModel.swift
│ │ └── RefreshErrorViewModel.swift
│ └── Views
│ │ ├── ContentView
│ │ ├── ContentView.swift
│ │ ├── FilterListRow.swift
│ │ ├── FilterListView.swift
│ │ └── StatsView.swift
│ │ ├── Error
│ │ └── ErrorView.swift
│ │ ├── ExtensionCheck
│ │ └── EnableExtensionsSheet.swift
│ │ ├── Help
│ │ └── HelpSheet.swift
│ │ ├── Import
│ │ └── ImportView.swift
│ │ ├── Logs
│ │ └── LogsView.swift
│ │ ├── Settings
│ │ └── SettingsView.swift
│ │ ├── Shared
│ │ └── PulsatingCircleButton.swift
│ │ └── Sidebar
│ │ └── SidebarView.swift
└── WebShieldApp.entitlements
├── WebShieldCookies
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldCustom
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldExperimental
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldMultipurpose
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldPrivacy
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldRegional
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
├── WebShieldSecurity
├── Info.plist
├── Source
│ └── ContentBlockerRequestHandler.swift
└── blockerList.json
└── WebShieldSocial
├── Info.plist
├── Source
└── ContentBlockerRequestHandler.swift
└── blockerList.json
/.build/workspace-state.json:
--------------------------------------------------------------------------------
1 | {
2 | "object" : {
3 | "artifacts" : [
4 |
5 | ],
6 | "dependencies" : [
7 |
8 | ]
9 | },
10 | "version" : 6
11 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [arjpar]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: imarjuna
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: imarjuna
10 | issuehunt: # Replace with a single IssueHunt username
11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: imarjuna
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report-for-webshield.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report for WebShield
3 | about: "**These do not have to do with bugs with filters or broken websites.** Create
4 | a report to help us improve."
5 | title: "[BUG]"
6 | labels: bug
7 | assignees: ''
8 |
9 | ---
10 |
11 | **These do not have to do with bugs with filters or broken websites. If it does, it will be closed.**
12 |
13 | **Before opening a new issue, please search existing issues. If it is a duplicate it will be closed.**
14 |
15 | **Describe the bug**
16 | A clear and concise description of what the bug is.
17 |
18 | **To Reproduce**
19 | Steps to reproduce the behavior:
20 | 1. Go to '...'
21 | 2. Click on '....'
22 | 3. Scroll down to '....'
23 | 4. See error
24 |
25 | **Expected behavior**
26 | A clear and concise description of what you expected to happen.
27 |
28 | **Screenshots**
29 | If applicable, add screenshots to help explain your problem.
30 |
31 | **Desktop (please complete the following information):**
32 | - OS: [e.g. iOS]
33 | - Browser [e.g. chrome, safari]
34 | - Version [e.g. 22]
35 |
36 | **Smartphone (please complete the following information):**
37 | - Device: [e.g. iPhone6]
38 | - OS: [e.g. iOS8.1]
39 | - Browser [e.g. stock browser, safari]
40 | - Version [e.g. 22]
41 |
42 | **Additional context**
43 | Add any other context about the problem here.
44 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request-for-webshield.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request for WebShield
3 | about: "**These do not have to do with bugs with filters or broken websites.** Suggest
4 | an idea for this project."
5 | title: "[FR]"
6 | labels: feature request
7 | assignees: arjpar
8 |
9 | ---
10 |
11 | **These do not have to do with bugs with filters or broken websites. If it does, it will be closed.**
12 |
13 | **Before opening a new issue, please search existing issues. If it is a duplicate it will be closed.**
14 |
15 | **Is your feature request related to a problem? Please describe.**
16 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
17 |
18 | **Describe the solution you'd like**
19 | A clear and concise description of what you want to happen.
20 |
21 | **Describe alternatives you've considered**
22 | A clear and concise description of any alternative solutions or features you've considered.
23 |
24 | **Additional context**
25 | Add any other context or screenshots about the feature request here.
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | combined.txt
2 | c.sh
3 | .vscode/
4 | # Xcode
5 | #
6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
7 |
8 | ## User settings
9 | xcuserdata/
10 |
11 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
12 | *.xcscmblueprint
13 | *.xccheckout
14 |
15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
16 | build/
17 | DerivedData/
18 | *.moved-aside
19 | *.pbxuser
20 | !default.pbxuser
21 | *.mode1v3
22 | !default.mode1v3
23 | *.mode2v3
24 | !default.mode2v3
25 | *.perspectivev3
26 | !default.perspectivev3
27 |
28 | ## Obj-C/Swift specific
29 | *.hmap
30 |
31 | ## App packaging
32 | *.ipa
33 | *.dSYM.zip
34 | *.dSYM
35 |
36 | ## Playgrounds
37 | timeline.xctimeline
38 | playground.xcworkspace
39 |
40 | # Swift Package Manager
41 | #
42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
43 | # Packages/
44 | # Package.pins
45 | # Package.resolved
46 | # *.xcodeproj
47 | #
48 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
49 | # hence it is not needed unless you have added a package configuration file to your project
50 | # .swiftpm
51 |
52 | .build/
53 |
54 | # CocoaPods
55 | #
56 | # We recommend against adding the Pods directory to your .gitignore. However
57 | # you should judge for yourself, the pros and cons are mentioned at:
58 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
59 | #
60 | # Pods/
61 | #
62 | # Add this line if you want to avoid checking in source code from the Xcode workspace
63 | # *.xcworkspace
64 |
65 | # Carthage
66 | #
67 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
68 | # Carthage/Checkouts
69 |
70 | Carthage/Build/
71 |
72 | # Accio dependency management
73 | Dependencies/
74 | .accio/
75 |
76 | # fastlane
77 | #
78 | # It is recommended to not store the screenshots in the git repo.
79 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
80 | # For more information about the recommended setup visit:
81 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
82 |
83 | fastlane/report.xml
84 | fastlane/Preview.html
85 | fastlane/screenshots/**/*.png
86 | fastlane/test_output
87 |
88 | # Code Injection
89 | #
90 | # After new code Injection tools there's a generated folder /iOSInjectionProject
91 | # https://github.com/johnno1962/injectionforxcode
92 |
93 | iOSInjectionProject/
94 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | ## Preamble
4 |
5 | This Code of Conduct aims to cultivate a welcoming, inclusive, and productive environment for everyone participating in our project, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We are committed to fostering a community where everyone feels safe, respected, and empowered to contribute.
6 |
7 | ## Our Values
8 |
9 | We believe that respectful communication, constructive collaboration, and a shared commitment to project goals are essential for a thriving community. We encourage:
10 |
11 | - **Empathy and Kindness:** Treat everyone with respect and consideration, valuing diverse perspectives and experiences.
12 | - **Collaboration and Openness:** Work together constructively, embracing open communication and feedback.
13 | - **Professionalism and Integrity:** Maintain a professional demeanor and act with integrity in all interactions.
14 | - **Growth and Learning:** Foster a supportive environment for learning and growth, both individually and as a community.
15 | - **Accountability and Responsibility:** Take ownership of your actions and their impact on others.
16 | - **Community First:** The needs and well-being of the overall community come first.
17 |
18 | ## Unacceptable Behavior
19 |
20 | The following behaviors are unacceptable and will not be tolerated:
21 |
22 | - **Harassment:** Including, but not limited to, offensive verbal or written comments related to personal characteristics, deliberate intimidation, stalking, following, harassing photography or recording, sustained disruption of talks or other events, inappropriate physical contact, and unwelcome sexual attention.
23 | - **Discrimination:** Excluding, marginalizing, or otherwise disadvantaging individuals based on any protected characteristic.
24 | - **Violence and Threats of Violence:** Any form of violence, threat of violence, or incitement of violence is strictly prohibited.
25 | - **Insults and Personal Attacks:** Avoid personal attacks, insults, and derogatory language.
26 | - **Doxing and Privacy Violations:** Sharing private information of others without their explicit consent.
27 | - **Disruptive Behavior:** Trolling, spamming, or any behavior that disrupts the project's communication channels or events.
28 | - **Unprofessional Conduct:** Actions that are inappropriate in a professional setting, such as sexualized language or imagery.
29 | - **Illegal Activities:** Engaging in any illegal activity is prohibited.
30 |
31 | ## Reporting and Enforcement
32 |
33 | If you experience or witness a violation of this Code of Conduct, please report it to the project maintainers at [ap2045202@gmail.com](mailto:ap2045202@gmail.com). All reports will be handled with discretion and confidentiality.
34 |
35 | Project maintainers are responsible for enforcing this Code of Conduct and will take appropriate action based on the severity of the violation. Possible actions include:
36 |
37 | - **Correction:** A private communication clarifying the nature of the violation and expectations for future behavior.
38 | - **Written Warning:** A formal written warning documenting the violation and consequences for repeated offenses.
39 | - **Temporary Ban:** Temporary suspension from project spaces, such as communication channels and events.
40 | - **Permanent Ban:** Permanent exclusion from the project.
41 |
42 | Project maintainers will strive to be fair and impartial in their enforcement decisions, and will document their actions and reasoning. Individuals who believe they have been unjustly sanctioned may appeal the decision to [ap2045202@gmail.com](mailto:ap2045202@gmail.com).
43 |
44 | ## Scope
45 |
46 | This Code of Conduct applies to all project spaces, including but not limited to:
47 |
48 | - Code repositories
49 | - Issue trackers
50 | - Mailing lists
51 | - Chat platforms
52 | - Forums
53 | - Online and offline events related to the project
54 |
55 | This Code of Conduct also applies when an individual is representing the project or its community in public spaces.
56 |
57 | ## Attribution
58 |
59 | This Code of Conduct is inspired by and draws upon elements from the Contributor Covenant, various other open-source project codes of conduct, and best practices for creating inclusive and respectful online communities. It is a living document and may be updated from time to time.
60 |
--------------------------------------------------------------------------------
/CodingStyle.md:
--------------------------------------------------------------------------------
1 | TODO
2 |
--------------------------------------------------------------------------------
/ISSUES.md:
--------------------------------------------------------------------------------
1 | # Issue Reporting Guidelines
2 |
3 | Thank you for using WebShield! Before submitting an issue, please take a moment to read through these guidelines to ensure that your report is actionable and helps us improve the project efficiently.
4 |
5 | ## Types of Issues to Report
6 |
7 | ### **Bug Reports**
8 | We handle issues related to WebShield itself (e.g., performance, incorrect rule application, crashes) but **do not maintain filter lists**. If you are experiencing problems with specific websites (ads not being blocked or incorrect blocking), the issue might lie with the **filter list**, and you should report it to the respective filter list maintainer (e.g., [EasyList](https://easylist.to)). Ensure that the only extension being used is WebShield, and you are using Safari **not** Safari TP.
9 |
10 | Examples of WebShield-related bugs:
11 | - WebShield crashes or causes Safari to slow down.
12 | - Filter rules are not being applied properly (e.g., syntax errors or rule implementation issues).
13 | - UI or configuration issues (e.g., adding/removing filter lists not working as expected).
14 |
15 | ### **Feature Requests**
16 | If you have an idea for improving WebShield, we’d love to hear about it! Please ensure that your request is aligned with the project's goals and current scope (Safari ad-blocking, privacy protection, and performance).
17 |
18 | ## Before Submitting an Issue
19 |
20 | Please complete the following checklist before opening an issue:
21 |
22 | - [ ] **Check for duplicates**: Search the [issue tracker](https://github.com/user/webshield/issues) to see if the issue has already been reported or addressed.
23 | - [ ] **Update to the latest version**: Ensure you are using the latest version of WebShield. Issues reported for outdated versions may be closed.
24 | - [ ] **Verify the issue is related to WebShield**: If the issue is specific to a filter list (e.g., ads not being blocked), it should be reported to the list maintainer, not WebShield.
25 |
26 | ## Bug Report Template
27 |
28 | Please follow this template to ensure we can understand and replicate the issue:
29 |
30 | ### Bug Summary
31 |
32 | A clear and concise description of what the bug is.
33 |
34 | **Steps to Reproduce**:
35 | (Example)
36 | 1. Go to '...'
37 | 2. Click on '...'
38 | 3. Scroll down to '...'
39 | 4. See error
40 |
41 | **Expected Behavior**:
42 | What you expected to happen.
43 |
44 | **Actual Behavior**:
45 | What actually happened.
46 |
47 | **Environment**:
48 | - macOS/iOS/iPadOS/visionOS version:
49 | - Safari version:
50 | - WebShield version:
51 | - Filter lists being used (include links or names):
52 | - Any other extensions being used alongside WebShield:
53 |
54 | **Additional Context (Logs, Screenshots, etc.)**:
55 | Please include any relevant logs, screenshots, or screen recordings that may help us debug the issue. Logs can be accessed by following these steps:
56 | 1. Open WebShield.
57 | 2. Navigate to **Preferences** > **Debug**.
58 | 3. Copy the log output and paste it here.
59 |
60 | ## Feature Request Template
61 |
62 | We are always open to considering new features that improve WebShield. Please follow this template to submit a feature request:
63 |
64 | ### Feature Summary
65 |
66 | A concise description of the feature you are proposing.
67 |
68 | **Use Case**:
69 | Explain why this feature is needed and how it would improve WebShield. Provide examples if possible.
70 |
71 | **Proposed Solution**:
72 | How should this feature work? Be as detailed as possible.
73 |
74 | **Alternatives Considered**:
75 | List any alternative solutions you have considered, if applicable.
76 |
77 | ## Where to Report Filter List Issues
78 |
79 | Since WebShield does not host or maintain filter lists, please report filter list-related issues directly to the maintainers of those lists.
80 |
81 | If the issue is due to an ad-blocking rule not being applied correctly due to a bug in WebShield, feel free to open an issue here and include relevant details.
82 |
83 | ## Additional Resources
84 |
85 | - **WebShield Documentation**: [Link to documentation](https://github.com/webshieldapp/webshield/wiki)
86 | - **GitHub Discussions**: [Join the conversation](https://github.com/webshieldapp/webshield/discussions)
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Testflight for WebShield
2 |
3 | https://testflight.apple.com/join/1t5HfEGS
4 |
5 | > [!TIP]
6 | >
7 | > - [Here](http://discord.com/invite/gQ4ygPKyur) is the Discord if you want to communicate through instant messaging and get real time updates on the project.
8 | > - [Here](https://github.com/WebShieldApp/WebShield/discussions/) are general announcements and discussions.
9 |
10 |
11 |
12 |
13 |
14 |
15 | # WebShield - Safari Adblocker
16 |
17 | ### Introduction
18 |
19 | WebShield is a wide-spectrum content blocker, akin to uBlock Origin, specifically for Safari.
20 |
21 | > [!IMPORTANT]
22 | >
23 | > **Note:** WebShield is currently in a beta stage of maturity. Features and documentation are subject to change as we continue to develop and improve the project.
24 |
25 | ## Features
26 |
27 | WebShield can block ads, trackers, cookie notices, and more. You can also import custom filter lists. WebShield supports Safari's Content Blocking API as well as scriptlets & extended css for more advanced, targeted blocking.
28 |
29 | ## System Requirements
30 |
31 | These requirements are tentative and due to change. I will try to support older OSes as much as I can.
32 |
33 | - macOS 14 or later
34 | - iOS 17 or later
35 | - visionOS 1.3 or later
36 |
37 | ## Installation
38 |
39 | ### Testflight
40 |
41 | 1. Download & install WebShield from [TestFlight](https://testflight.apple.com/join/1t5HfEGS)
42 | 1. (macOS) Open Safari and navigate to Settings > Extensions; (iOS) Open Settings > Safari > Extensions
43 | 1. Enable all "WebShield" extensions
44 | 1. (macOS) Go to Websites (in Safai Settings) > "WebShield Advanced"
45 | 1. (macOS) Change "For other websites" to "Allow"
46 | 1. (iOS) Go to "WebShield Advanced" (in Settings > Safari > Extensions)
47 | 1. (iOS) Change "All Websites" to "Allow"
48 |
49 | ### App Store (Coming Soon)
50 |
51 | An App Store release will happen when we reach a stable level of maturity.
52 |
53 | ### From Source (Coming Soon)
54 |
55 | For developers and advanced users who want to build from source:
56 |
57 | 1. Clone the repository:
58 | ```bash
59 | git clone https://github.com/WebShieldApp/WebShield.git
60 | ```
61 | 2. Open the project in Xcode and build the app for your platform.
62 | 3. Follow platform-specific steps for enabling the extension in Safari.
63 |
64 | _Note: Detailed installation instructions will be added once the project reaches a stable release._
65 |
66 | ## Usage
67 |
68 | There will be documentation on usage soon.
69 |
70 | ## Contributing
71 |
72 | We welcome contributions to WebShield! Here's how you can help:
73 |
74 | 1. **Bug Reports & Feature Requests:** Use the GitHub Issues to report bugs or request features.
75 | 2. **Code Contributions:** Fork the repository, create a feature branch, and submit a pull request.
76 | 3. **Documentation Improvements:** Found a typo or have better wording suggestions? Feel free to submit a PR!
77 |
78 | For detailed guidelines, please see our [CONTRIBUTING.md](CONTRIBUTING.md) file for details on how to get started.
79 |
80 | ## License
81 |
82 | This project is licensed under the GNU GPLv3 License - see the [LICENSE](LICENSE) file for details.
83 |
84 | ## Support
85 |
86 | If you need help or have any questions, you can reach out through:
87 |
88 | - [Discord](http://discord.com/invite/gQ4ygPKyur)
89 | - [GitHub Discussions](https://github.com/WebShieldApp/WebShield/discussions)
90 | - Email: [ap2045202@gmail.com](mailto:ap2045202@gmail.com)
91 |
92 | ## Acknowledgments
93 |
94 | WebShield draws inspiration from privacy and performance-focused projects like uBlock Origin. This project wouldn't be possible without the dedication of filter list maintainers who work tirelessly to keep the web clean. I'm deeply grateful to stand on the shoulders of these projects and their contributors. Special thanks to AdGuard, whose tools — particularly SafariConverterLib and Scriptlets/ExtendedCSS — have been instrumental to WebShield's development. I'd also like to thank @0xCube for our ongoing collaboration and the valuable connection between our projects.
95 |
96 | ## Financial Contributions and Support
97 |
98 | If you would like to thank me for building WebShield here are a few links:
99 |
100 | - Ko-Fi: [@imarjuna](https://ko-fi.com/imarjuna)
101 | - Buy Me a Coffee: [@imarjuna](https://buymeacoffee.com/imarjuna)
102 | - GitHub Sponsors: [@arjpar](https://github.com/sponsors/arjpar)
103 | - LiberaPay: [@imarjuna](https://liberapay.com/imarjuna/)
104 |
105 | ---
106 |
107 | Happy browsing! 🛡️
108 |
--------------------------------------------------------------------------------
/Shared/App+ContentBlocker+Extension.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.dev.arjuna.WebShield
10 |
11 | com.apple.security.files.user-selected.read-only
12 |
13 | com.apple.security.network.client
14 |
15 | com.apple.security.network.server
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Tests/WebShieldAppTests/AppTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppTests.swift
3 | // AppTests
4 | //
5 | // Created by Arjun on 1/7/25.
6 | //
7 |
8 | import Testing
9 |
10 | struct AppTests {
11 |
12 | @Test func example() async throws {
13 | // Write your test here and use APIs like `#expect(...)` to check expected conditions.
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/Tests/WebShieldAppUITests/AppUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppUITests.swift
3 | // AppUITests
4 | //
5 | // Created by Arjun on 1/7/25.
6 | //
7 |
8 | import XCTest
9 |
10 | final class AppUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | @MainActor
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | @MainActor
35 | func testLaunchPerformance() throws {
36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
37 | // This measures how long it takes to launch your application.
38 | measure(metrics: [XCTApplicationLaunchMetric()]) {
39 | XCUIApplication().launch()
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Tests/WebShieldAppUITests/AppUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppUITestsLaunchTests.swift
3 | // AppUITests
4 | //
5 | // Created by Arjun on 1/7/25.
6 | //
7 |
8 | import XCTest
9 |
10 | final class AppUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | @MainActor
21 | func testLaunch() throws {
22 | let app = XCUIApplication()
23 | app.launch()
24 |
25 | // Insert steps here to perform after app launch but before taking a screenshot,
26 | // such as logging into a test account or navigating somewhere in the app
27 |
28 | let attachment = XCTAttachment(screenshot: app.screenshot())
29 | attachment.name = "Launch Screen"
30 | attachment.lifetime = .keepAlways
31 | add(attachment)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "b3469bf4db7eca006e3f05526b66afddbb35458e8f1d2ce6706d9665c94a4a64",
3 | "pins" : [
4 | {
5 | "identity" : "punycodeswift",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/gumob/PunycodeSwift.git",
8 | "state" : {
9 | "revision" : "30a462bdb4398ea835a3585472229e0d74b36ba5",
10 | "version" : "3.0.0"
11 | }
12 | },
13 | {
14 | "identity" : "safariconverterlib",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/AdguardTeam/SafariConverterLib.git",
17 | "state" : {
18 | "branch" : "master",
19 | "revision" : "a9e6920ea68145c6a98fd79acd86c587ec7e0b7b"
20 | }
21 | },
22 | {
23 | "identity" : "swift-argument-parser",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/apple/swift-argument-parser",
26 | "state" : {
27 | "revision" : "41982a3656a71c768319979febd796c6fd111d5c",
28 | "version" : "1.5.0"
29 | }
30 | }
31 | ],
32 | "version" : 3
33 | }
34 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/project.xcworkspace/xcuserdata/a.xcuserdatad/IDEFindNavigatorScopes.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/project.xcworkspace/xcuserdata/a.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | ShowSharedSchemesAutomaticallyEnabled
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/AppTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
17 |
20 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
47 |
48 |
50 |
51 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Ads.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Advanced.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
45 |
46 |
54 |
55 |
61 |
62 |
63 |
64 |
66 |
67 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Annoyances.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Cookies.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Custom.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Experimental.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Multipurpose.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Privacy.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Regional.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Security.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield Social.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 |
17 |
23 |
24 |
25 |
31 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
60 |
62 |
68 |
69 |
70 |
71 |
79 |
81 |
87 |
88 |
89 |
90 |
92 |
93 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcshareddata/xcschemes/WebShield.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
35 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcuserdata/a.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/WebShield.xcodeproj/xcuserdata/a.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | App (iOS).xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 4
11 |
12 | App (macOS).xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 5
16 |
17 | AppTests.xcscheme_^#shared#^_
18 |
19 | orderHint
20 | 12
21 |
22 | WebShield Ads.xcscheme_^#shared#^_
23 |
24 | orderHint
25 | 1
26 |
27 | WebShield Advanced.xcscheme_^#shared#^_
28 |
29 | orderHint
30 | 11
31 |
32 | WebShield Annoyances.xcscheme_^#shared#^_
33 |
34 | orderHint
35 | 7
36 |
37 | WebShield Cookies.xcscheme_^#shared#^_
38 |
39 | orderHint
40 | 5
41 |
42 | WebShield Custom.xcscheme_^#shared#^_
43 |
44 | orderHint
45 | 10
46 |
47 | WebShield Experimental.xcscheme_^#shared#^_
48 |
49 | orderHint
50 | 9
51 |
52 | WebShield Multipurpose.xcscheme_^#shared#^_
53 |
54 | orderHint
55 | 4
56 |
57 | WebShield Privacy.xcscheme_^#shared#^_
58 |
59 | orderHint
60 | 2
61 |
62 | WebShield Regional.xcscheme_^#shared#^_
63 |
64 | orderHint
65 | 8
66 |
67 | WebShield Security.xcscheme_^#shared#^_
68 |
69 | orderHint
70 | 3
71 |
72 | WebShield Social.xcscheme_^#shared#^_
73 |
74 | orderHint
75 | 6
76 |
77 | WebShield.xcscheme_^#shared#^_
78 |
79 | orderHint
80 | 0
81 |
82 | a.xcscheme_^#shared#^_
83 |
84 | orderHint
85 | 3
86 |
87 |
88 | SuppressBuildableAutocreation
89 |
90 | 00026EB52C2B96740087E1DA
91 |
92 | primary
93 |
94 |
95 | 000F2C9F2D2E533400871D5E
96 |
97 | primary
98 |
99 |
100 | 000F2CB02D2E533500871D5E
101 |
102 | primary
103 |
104 |
105 | 000F2CBA2D2E533500871D5E
106 |
107 | primary
108 |
109 |
110 | 000F2CCF2D2E534600871D5E
111 |
112 | primary
113 |
114 |
115 | 000F2CE32D2E535400871D5E
116 |
117 | primary
118 |
119 |
120 | 001848612C2956AC00EE2E7A
121 |
122 | primary
123 |
124 |
125 | 00333FA92C3CC52E0031AEC6
126 |
127 | primary
128 |
129 |
130 | 003BEF6D2C2BC10800CCC3EA
131 |
132 | primary
133 |
134 |
135 | 006959532C427DC0008A6204
136 |
137 | primary
138 |
139 |
140 | 006FC39B2D3095DD0091056F
141 |
142 | primary
143 |
144 |
145 | 008F153C2C2D95DC000BEE0C
146 |
147 | primary
148 |
149 |
150 | 00A319552C3CAE9D007743FB
151 |
152 | primary
153 |
154 |
155 | 00C2BE0B2D2E31F9000B431F
156 |
157 | primary
158 |
159 |
160 | 00C2BE302D2E327F000B431F
161 |
162 | primary
163 |
164 |
165 | 00C356262D2EEFE000BACCE7
166 |
167 | primary
168 |
169 |
170 | 00E718602D3452EC00FD22A7
171 |
172 | primary
173 |
174 |
175 | 00E718742D3453A700FD22A7
176 |
177 | primary
178 |
179 |
180 | 00E718882D3453BC00FD22A7
181 |
182 | primary
183 |
184 |
185 | 00E7189C2D3453CF00FD22A7
186 |
187 | primary
188 |
189 |
190 | 00E718B02D3453E600FD22A7
191 |
192 | primary
193 |
194 |
195 | 00E718C42D3453FF00FD22A7
196 |
197 | primary
198 |
199 |
200 | 00E718D82D34542600FD22A7
201 |
202 | primary
203 |
204 |
205 | 00E718EC2D34543F00FD22A7
206 |
207 | primary
208 |
209 |
210 | 00E719002D34545D00FD22A7
211 |
212 | primary
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/WebShieldAds/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Ads" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldAds/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Ads", category: "ContentBlockerRequestHandler")
5 |
6 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
7 | func beginRequest(with context: NSExtensionContext) {
8 | let requiredPart = "group.dev.arjuna.WebShield"
9 |
10 | logger.log("Content Blocker logging...")
11 |
12 | guard
13 | let containerURL = FileManager.default.containerURL(
14 | forSecurityApplicationGroupIdentifier: requiredPart)
15 | else {
16 | logger.log(
17 | "Error: Could not get container URL for group \(requiredPart)")
18 | context.completeRequest(returningItems: nil, completionHandler: nil)
19 | return
20 | }
21 |
22 | logger.log("Successfully got container URL for group \(requiredPart)")
23 |
24 | let blockerlistURL = containerURL.appendingPathComponent(
25 | "ads.json")
26 |
27 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
28 | logger.log("Content Blocker Error: ads.json does not exist")
29 | context.completeRequest(returningItems: nil, completionHandler: nil)
30 | return
31 | }
32 |
33 | logger.log("ads.json exists")
34 |
35 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
36 | logger.log("Sending attachment")
37 | let item = NSExtensionItem()
38 | item.attachments = [attachment]
39 | context.completeRequest(
40 | returningItems: [item], completionHandler: nil)
41 | logger.log("Completed request")
42 | } else {
43 | logger.log(
44 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
45 | context.completeRequest(returningItems: nil, completionHandler: nil)
46 | }
47 |
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/WebShieldAds/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.web-extension
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).SafariWebExtensionHandler
33 | SFSafariWebsiteAccess
34 |
35 | Level
36 | All
37 |
38 |
39 | NSHumanReadableDescription
40 | Advanced wide-spectrum content blocking for WebShield. Blocks complex ads, trackers, miners, and more (including YouTube ads).
41 |
42 |
43 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extension_name": {
3 | "message": "WebShield Advanced",
4 | "description": "The display name for the extension."
5 | },
6 | "extension_description": {
7 | "message": "Advanced wide-spectrum content blocking for WebShield. Blocks complex ads, trackers, miners, and more.",
8 | "description": "Description of what the extension does."
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/images/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldAdvanced/Resources/images/icon-128.png
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/images/icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldAdvanced/Resources/images/icon-256.png
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/images/icon-48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldAdvanced/Resources/images/icon-48.png
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/images/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldAdvanced/Resources/images/icon-512.png
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/images/icon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldAdvanced/Resources/images/icon-64.png
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/images/icon-96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldAdvanced/Resources/images/icon-96.png
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/images/toolbar-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "default_locale": "en",
4 |
5 | "name": "__MSG_extension_name__",
6 | "description": "__MSG_extension_description__",
7 | "version": "1.0.0",
8 |
9 | "icons": {
10 | "48": "images/icon-48.png",
11 | "96": "images/icon-96.png",
12 | "128": "images/icon-128.png",
13 | "256": "images/icon-256.png",
14 | "512": "images/icon-512.png"
15 | },
16 |
17 | "background": {
18 | "scripts": ["src/background.js"],
19 | "persistent": false
20 | },
21 |
22 | "content_scripts": [
23 | {
24 | "matches": [""],
25 | "js": [
26 | "src/scriptlets/scriptlets.js",
27 | "src/extendedCss/extended-css.js",
28 | "src/content.js"
29 | ],
30 | "all_frames": true,
31 | "match_about_blank": true,
32 | "run_at": "document_start"
33 | }
34 | ],
35 |
36 | "browser_action": {
37 | "default_popup": "popup/popup.html",
38 | "default_icon": "images/toolbar-icon.svg"
39 | },
40 |
41 | "permissions": ["nativeMessaging", "", "storage", "tabs", "runtime"]
42 | }
43 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/popup/popup.css:
--------------------------------------------------------------------------------
1 | :root {
2 | color-scheme: light dark;
3 | --link-color: #007aff;
4 | --hover-background: rgba(0, 0, 0, 0.05);
5 | --active-background: rgba(0, 0, 0, 0.1);
6 | }
7 |
8 | @media (prefers-color-scheme: dark) {
9 | :root {
10 | --link-color: #0a84ff;
11 | --hover-background: rgba(255, 255, 255, 0.05);
12 | --active-background: rgba(255, 255, 255, 0.1);
13 | }
14 | }
15 |
16 | body {
17 | width: 200px;
18 | padding: 8px 0;
19 | margin: 0;
20 | font-family: -apple-system, system-ui, sans-serif;
21 | background: transparent;
22 | }
23 |
24 | .menu-list {
25 | display: block;
26 | }
27 |
28 | .menu-list ul {
29 | list-style: none;
30 | margin: 0;
31 | padding: 0;
32 | }
33 |
34 | .menu-item {
35 | display: flex;
36 | align-items: center;
37 | padding: 8px 16px;
38 | text-decoration: none;
39 | color: var(--link-color);
40 | transition: background-color 0.2s ease;
41 | -webkit-tap-highlight-color: transparent;
42 | }
43 |
44 | .menu-item:hover {
45 | background-color: var(--hover-background);
46 | }
47 |
48 | .menu-item:active {
49 | background-color: var(--active-background);
50 | }
51 |
52 | .menu-label {
53 | font-size: 0.875rem;
54 | line-height: 1.25;
55 | letter-spacing: -0.005rem;
56 | font-weight: 500;
57 | text-align: left;
58 | flex: 1;
59 | }
60 |
61 | /* Platform adjustments */
62 | @supports (-webkit-touch-highlight: none) {
63 | .menu-item {
64 | padding: 10px 16px;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/popup/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Resources/popup/popup.js:
--------------------------------------------------------------------------------
1 | console.log("(WebShield Advanced) Popup");
2 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Source/ChunkFileReader.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | final class ChunkFileReader {
4 | private let fileURL: URL
5 | private let chunkSize: Int
6 | private let totalSize: UInt64
7 | private var currentOffset: UInt64 = 0
8 | private var fileHandle: FileHandle
9 |
10 | init(fileURL: URL, chunkSize: Int = 32768) throws {
11 | self.fileURL = fileURL
12 | self.chunkSize = chunkSize
13 | self.totalSize = try FileManager.default.attributesOfItem(atPath: fileURL.path)[.size] as? UInt64 ?? 0
14 | // Open the file handle once and store it.
15 | self.fileHandle = try FileHandle(forReadingFrom: fileURL)
16 | }
17 |
18 | deinit {
19 | // Ensure the file handle is closed when the actor is deallocated.
20 | try? fileHandle.close()
21 | }
22 |
23 | /// Reads the next chunk from the file as a UTF-8 String.
24 | func nextChunk() -> String? {
25 | // If we've reached or exceeded the total file size, there's nothing more to read.
26 | guard currentOffset < totalSize else { return nil }
27 |
28 | do {
29 | // Seek to the current offset.
30 | try fileHandle.seek(toOffset: currentOffset)
31 | // Read the next chunk.
32 | if let data = try fileHandle.read(upToCount: chunkSize), !data.isEmpty {
33 | // Update the offset based on the number of bytes actually read.
34 | currentOffset += UInt64(data.count)
35 | return String(decoding: data, as: UTF8.self)
36 | } else {
37 | return nil
38 | }
39 | } catch {
40 | print("Error reading chunk: \(error)")
41 | return nil
42 | }
43 | }
44 |
45 | /// Resets the file reading to the beginning.
46 | func rewind() {
47 | currentOffset = 0
48 | do {
49 | try fileHandle.seek(toOffset: 0)
50 | } catch {
51 | print("Error rewinding: \(error)")
52 | }
53 | }
54 |
55 | /// Returns the progress of file reading as a fraction between 0 and 1.
56 | var progress: Double {
57 | return Double(currentOffset) / Double(totalSize)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Source/ContentBlockerEngineWrapper.swift:
--------------------------------------------------------------------------------
1 | // ContentBlockerEngineWrapper.swift
2 | import ContentBlockerEngine
3 | import CryptoKit
4 | import Foundation
5 | import OSLog
6 |
7 | // Protocol defining engine behavior
8 | protocol EngineHandling {
9 | func getBlockingData(for url: URL) throws -> String
10 | func getCurrentRulesData() throws -> String
11 | }
12 |
13 | // Protocol for rules update notification
14 | protocol RulesUpdateNotifying {
15 | var onRulesUpdated: ((String) -> Void)? { get set }
16 | }
17 |
18 | // MARK: - ContentBlockerEngineWrapper
19 | final class ContentBlockerEngineWrapper: EngineHandling, RulesUpdateNotifying {
20 | private let jsonURL: URL
21 | private let logger = Logger(subsystem: "dev.arjuna.WebShield", category: "Engine")
22 | private var engine: ContentBlockerEngine?
23 | private var lastHash: String?
24 | private var cachedJSONString: String?
25 | private var fileMonitor: DispatchSourceFileSystemObject?
26 | private var fileDescriptor: Int32?
27 |
28 | // Rules update notification callback
29 | var onRulesUpdated: ((String) -> Void)?
30 |
31 | init(appGroupID: String) throws {
32 | guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)
33 | else {
34 | throw EngineError.appGroupNotFound(appGroupID)
35 | }
36 |
37 | self.jsonURL = containerURL.appendingPathComponent("advancedBlocking.json")
38 |
39 | // Inline file existence check instead of calling actor-isolated method
40 | guard FileManager.default.fileExists(atPath: jsonURL.path) else {
41 | throw EngineError.fileNotFound(jsonURL)
42 | }
43 |
44 | // Set up file monitoring
45 | setupFileMonitoring()
46 | }
47 |
48 | deinit {
49 | stopFileMonitoring()
50 | }
51 |
52 | private func setupFileMonitoring() {
53 | // Get file descriptor for monitoring
54 | fileDescriptor = open(jsonURL.path, O_EVTONLY)
55 | guard let fd = fileDescriptor else {
56 | logger.error("Failed to open file for monitoring")
57 | return
58 | }
59 |
60 | // Create dispatch source for file monitoring
61 | fileMonitor = DispatchSource.makeFileSystemObjectSource(
62 | fileDescriptor: fd,
63 | eventMask: .write,
64 | queue: .global()
65 | )
66 |
67 | fileMonitor?.setEventHandler { [weak self] in
68 | self?.handleFileChange()
69 | }
70 |
71 | fileMonitor?.setCancelHandler { [weak self] in
72 | if let fd = self?.fileDescriptor {
73 | close(fd)
74 | }
75 | }
76 |
77 | fileMonitor?.resume()
78 | }
79 |
80 | private func stopFileMonitoring() {
81 | fileMonitor?.cancel()
82 | fileMonitor = nil
83 | if let fd = fileDescriptor {
84 | close(fd)
85 | fileDescriptor = nil
86 | }
87 | }
88 |
89 | private func handleFileChange() {
90 | do {
91 | let data = try Data(contentsOf: jsonURL, options: .mappedIfSafe)
92 | let currentHash = SHA256.hash(data: data).hexString
93 |
94 | if lastHash != currentHash {
95 | // File has changed, update the engine and cache
96 | let jsonString = String(decoding: data, as: UTF8.self)
97 | cachedJSONString = jsonString
98 | engine = try ContentBlockerEngine(jsonString)
99 | lastHash = currentHash
100 |
101 | // Notify about the update
102 | onRulesUpdated?(jsonString)
103 | }
104 | } catch {
105 | logger.error("Error handling file change: \(error)")
106 | }
107 | }
108 |
109 | func getBlockingData(for url: URL) throws -> String {
110 | try reloadIfNeeded()
111 | return try engine?.getData(url: url) ?? ""
112 | }
113 |
114 | func getCurrentRulesData() throws -> String {
115 | if let cached = cachedJSONString {
116 | return cached
117 | }
118 |
119 | let data = try Data(contentsOf: jsonURL, options: .mappedIfSafe)
120 | let jsonString = String(decoding: data, as: UTF8.self)
121 | cachedJSONString = jsonString
122 | return jsonString
123 | }
124 |
125 | func makeChunkedReader() throws -> ChunkFileReader {
126 | try ChunkFileReader(fileURL: jsonURL)
127 | }
128 |
129 | private func reloadIfNeeded() throws {
130 | let data = try Data(contentsOf: jsonURL, options: .mappedIfSafe)
131 | let currentHash = SHA256.hash(data: data).hexString
132 |
133 | if cachedJSONString == nil || lastHash != currentHash || engine == nil {
134 | let jsonString = String(decoding: data, as: UTF8.self)
135 | cachedJSONString = jsonString
136 | engine = try ContentBlockerEngine(jsonString)
137 | lastHash = currentHash
138 | } else {
139 | logger.info("Using cached JSON data")
140 | }
141 | }
142 |
143 | private func validateFileExists() throws {
144 | guard FileManager.default.fileExists(atPath: jsonURL.path) else {
145 | throw EngineError.fileNotFound(jsonURL)
146 | }
147 | }
148 |
149 | enum EngineError: Error {
150 | case appGroupNotFound(String)
151 | case fileNotFound(URL)
152 | case engineInitialization(Error)
153 | }
154 | }
155 |
156 | // Extension to convert SHA256 hash to hex string
157 | extension SHA256.Digest {
158 | var hexString: String {
159 | self.map { String(format: "%02x", $0) }.joined()
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/WebShieldAdvanced/Source/NotificationManager.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import OSLog
3 | import SafariServices
4 |
5 | // Protocol defining notification behavior
6 | protocol NotificationHandling {
7 | func notifyRulesUpdate(_ rulesData: String)
8 | }
9 |
10 | final class NotificationManager: NotificationHandling {
11 | private let logger = Logger(subsystem: "dev.arjuna.WebShield", category: "NotificationManager")
12 | private let queue = DispatchQueue(label: "dev.arjuna.WebShield.notificationQueue", qos: .userInitiated)
13 | private var pendingNotifications: [String] = []
14 | private var isProcessingNotifications = false
15 |
16 | func notifyRulesUpdate(_ rulesData: String) {
17 | queue.async { [weak self] in
18 | self?.handleRulesUpdate(rulesData)
19 | }
20 | }
21 |
22 | private func handleRulesUpdate(_ rulesData: String) {
23 | // Add the notification to the queue
24 | pendingNotifications.append(rulesData)
25 |
26 | // If we're not already processing notifications, start processing
27 | if !isProcessingNotifications {
28 | processNextNotification()
29 | }
30 | }
31 |
32 | private func processNextNotification() {
33 | guard !pendingNotifications.isEmpty else {
34 | isProcessingNotifications = false
35 | return
36 | }
37 |
38 | isProcessingNotifications = true
39 | let rulesData = pendingNotifications.removeFirst()
40 |
41 | // Create a notification context
42 | let context = NSExtensionContext()
43 | let item = NSExtensionItem()
44 | item.userInfo = [
45 | SFExtensionMessageKey: [
46 | "type": "rulesUpdated",
47 | "rulesData": rulesData
48 | ]
49 | ]
50 |
51 | // Send the notification
52 | context.completeRequest(returningItems: [item]) { [weak self] success in
53 | if !success {
54 | self?.logger.error("Failed to send rules update notification")
55 | }
56 |
57 | // Process the next notification
58 | self?.queue.async {
59 | self?.processNextNotification()
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/WebShieldAnnoyances/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Annoyances" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldAnnoyances/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Annoyances", category: "ContentBlockerRequestHandler")
6 | private let specificList = "annoyances.json"
7 |
8 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
9 | func beginRequest(with context: NSExtensionContext) {
10 | let requiredPart = "group.dev.arjuna.WebShield"
11 |
12 | logger.log("Content Blocker logging...")
13 |
14 | guard
15 | let containerURL = FileManager.default.containerURL(
16 | forSecurityApplicationGroupIdentifier: requiredPart)
17 | else {
18 | logger.log(
19 | "Error: Could not get container URL for group \(requiredPart)")
20 | context.completeRequest(returningItems: nil, completionHandler: nil)
21 | return
22 | }
23 |
24 | logger.log("Successfully got container URL for group \(requiredPart)")
25 |
26 | let blockerlistURL = containerURL.appendingPathComponent(
27 | "\(specificList)")
28 |
29 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
30 | logger.log("Content Blocker Error: \(specificList) does not exist")
31 | context.completeRequest(returningItems: nil, completionHandler: nil)
32 | return
33 | }
34 |
35 | logger.log("\(specificList) exists")
36 |
37 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
38 | logger.log("Sending attachment")
39 | let item = NSExtensionItem()
40 | item.attachments = [attachment]
41 | context.completeRequest(
42 | returningItems: [item], completionHandler: nil)
43 | logger.log("Completed request")
44 | } else {
45 | logger.log(
46 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
47 | context.completeRequest(returningItems: nil, completionHandler: nil)
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WebShieldAnnoyances/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "universal",
6 | "reference" : "secondaryLabelColor"
7 | },
8 | "idiom" : "universal"
9 | },
10 | {
11 | "appearances" : [
12 | {
13 | "appearance" : "luminosity",
14 | "value" : "dark"
15 | }
16 | ],
17 | "color" : {
18 | "platform" : "universal",
19 | "reference" : "secondaryLabelColor"
20 | },
21 | "idiom" : "universal"
22 | }
23 | ],
24 | "info" : {
25 | "author" : "xcode",
26 | "version" : 1
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/1024-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/1024-mac.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/128-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/128-mac.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/16-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/16-mac.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/256-mac 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/256-mac 1.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/256-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/256-mac.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/32-mac 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/32-mac 1.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/32-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/32-mac.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/512-mac 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/512-mac 1.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/512-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/512-mac.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/64-mac.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/64-mac.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "1024.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | },
9 | {
10 | "appearances" : [
11 | {
12 | "appearance" : "luminosity",
13 | "value" : "dark"
14 | }
15 | ],
16 | "filename" : "WebShield Final (No Text).png",
17 | "idiom" : "universal",
18 | "platform" : "ios",
19 | "size" : "1024x1024"
20 | },
21 | {
22 | "appearances" : [
23 | {
24 | "appearance" : "luminosity",
25 | "value" : "tinted"
26 | }
27 | ],
28 | "filename" : "WebShield Final (No Text) 1.png",
29 | "idiom" : "universal",
30 | "platform" : "ios",
31 | "size" : "1024x1024"
32 | },
33 | {
34 | "filename" : "16-mac.png",
35 | "idiom" : "mac",
36 | "scale" : "1x",
37 | "size" : "16x16"
38 | },
39 | {
40 | "filename" : "32-mac 1.png",
41 | "idiom" : "mac",
42 | "scale" : "2x",
43 | "size" : "16x16"
44 | },
45 | {
46 | "filename" : "32-mac.png",
47 | "idiom" : "mac",
48 | "scale" : "1x",
49 | "size" : "32x32"
50 | },
51 | {
52 | "filename" : "64-mac.png",
53 | "idiom" : "mac",
54 | "scale" : "2x",
55 | "size" : "32x32"
56 | },
57 | {
58 | "filename" : "128-mac.png",
59 | "idiom" : "mac",
60 | "scale" : "1x",
61 | "size" : "128x128"
62 | },
63 | {
64 | "filename" : "256-mac 1.png",
65 | "idiom" : "mac",
66 | "scale" : "2x",
67 | "size" : "128x128"
68 | },
69 | {
70 | "filename" : "256-mac.png",
71 | "idiom" : "mac",
72 | "scale" : "1x",
73 | "size" : "256x256"
74 | },
75 | {
76 | "filename" : "512-mac 1.png",
77 | "idiom" : "mac",
78 | "scale" : "2x",
79 | "size" : "256x256"
80 | },
81 | {
82 | "filename" : "512-mac.png",
83 | "idiom" : "mac",
84 | "scale" : "1x",
85 | "size" : "512x512"
86 | },
87 | {
88 | "filename" : "1024-mac.png",
89 | "idiom" : "mac",
90 | "scale" : "2x",
91 | "size" : "512x512"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/WebShield Final (No Text) 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/WebShield Final (No Text) 1.png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.appiconset/WebShield Final (No Text).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.appiconset/WebShield Final (No Text).png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "WebShield Final Layer 1 (Background).png",
5 | "idiom" : "vision",
6 | "scale" : "2x"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/WebShield Final Layer 1 (Background).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/WebShield Final Layer 1 (Background).png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.solidimagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.solidimagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.solidimagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "WebShield Final Layer 2 (Shield).png",
5 | "idiom" : "vision",
6 | "scale" : "2x"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/WebShield Final Layer 2 (Shield).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/WebShield Final Layer 2 (Shield).png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "WebShield Final Layer 1 (Background).png",
5 | "idiom" : "vision",
6 | "scale" : "2x"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/WebShield Final Layer 1 (Background).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/WebShield Final Layer 1 (Background).png
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebShieldApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "properties" : {
7 | "compression-type" : "automatic"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/WebShieldApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIconFile
6 | AppIcon
7 | ITSAppUsesNonExemptEncryption
8 |
9 | LSMinimumSystemVersion
10 | $(MACOSX_DEPLOYMENT_TARGET)
11 | NSSupportsAutomaticTermination
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/WebShieldApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/WebShieldApp/Resources/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom": "iphone",
6 | "filename" : "WebShield Final (No Text)-20@2x.png",
7 | "scale": "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom": "iphone",
12 | "filename" : "WebShield Final (No Text)-20@3x.png",
13 | "scale": "3x"
14 | },
15 | {
16 | "size" : "20x20",
17 | "idiom": "ipad",
18 | "filename" : "WebShield Final (No Text)-20.png",
19 | "scale": "1x"
20 | },
21 | {
22 | "size" : "20x20",
23 | "idiom": "ipad",
24 | "filename" : "WebShield Final (No Text)-20@2x.png",
25 | "scale": "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "WebShield Final (No Text)-29@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "29x29",
35 | "idiom" : "iphone",
36 | "filename" : "WebShield Final (No Text)-29@3x.png",
37 | "scale" : "3x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "WebShield Final (No Text)-40@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "40x40",
47 | "idiom" : "iphone",
48 | "filename" : "WebShield Final (No Text)-40@3x.png",
49 | "scale" : "3x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "WebShield Final (No Text)-60@2x.png",
55 | "scale" : "2x"
56 | },
57 | {
58 | "size" : "60x60",
59 | "idiom" : "iphone",
60 | "filename" : "WebShield Final (No Text)-60@3x.png",
61 | "scale" : "3x"
62 | },
63 | {
64 | "size" : "29x29",
65 | "idiom" : "ipad",
66 | "filename" : "WebShield Final (No Text)-29.png",
67 | "scale" : "1x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "WebShield Final (No Text)-29@2x.png",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "40x40",
77 | "idiom" : "ipad",
78 | "filename" : "WebShield Final (No Text)-40.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "WebShield Final (No Text)-40@2x.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "76x76",
89 | "idiom" : "ipad",
90 | "filename" : "WebShield Final (No Text)-76.png",
91 | "scale" : "1x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "WebShield Final (No Text)-76@2x.png",
97 | "scale" : "2x"
98 | },
99 | {
100 | "size" : "83.5x83.5",
101 | "idiom" : "ipad",
102 | "filename" : "WebShield Final (No Text)-83.5@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "1024x1024",
107 | "idiom" : "ios-marketing",
108 | "filename" : "WebShield Final (No Text)-1024.png",
109 | "scale" : "1x"
110 | }
111 | ],
112 | "info" : {
113 | "version" : 1,
114 | "author" : "xcode"
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-1024.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-20.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-20@2x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-20@3x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-29.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-29@2x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-29@3x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-40.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-40@2x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-40@3x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-60@2x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-60@3x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-76.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-76@2x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/iOS/AppIcon.appiconset/WebShield Final (No Text)-83.5@2x.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "WebShield Final (No Text)-16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "WebShield Final (No Text)-32.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "WebShield Final (No Text)-32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "WebShield Final (No Text)-64.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "WebShield Final (No Text)-128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "WebShield Final (No Text)-256.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "WebShield Final (No Text)-256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "WebShield Final (No Text)-512.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "WebShield Final (No Text)-512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "WebShield Final (No Text)-1024.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-1024.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-128.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-16.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-256.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-32.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-512.png
--------------------------------------------------------------------------------
/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arjpar/WebShield/63cac3c1a31ed9cef432b7acfab23f0f5b0bdc10/WebShieldApp/Resources/macOS/AppIcon.appiconset/WebShield Final (No Text)-64.png
--------------------------------------------------------------------------------
/WebShieldApp/Source/App/AppSettings.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @MainActor
4 | final class AppSettings {
5 | static let shared = AppSettings() // Singleton instance
6 |
7 | private init() {} // Private initializer to enforce singleton
8 |
9 | private let initialRefreshKey = "hasPerformedInitialRefresh"
10 | @Published var lastRefreshedEnabledFilters: Set = [] // Store identifiers of enabled filters
11 |
12 | @Published var hasPerformedInitialRefresh: Bool = false
13 |
14 | // var hasPerformedInitialRefresh: Bool {
15 | // get {
16 | // UserDefaults.standard.bool(forKey: initialRefreshKey)
17 | // }
18 | // set {
19 | // UserDefaults.standard.set(newValue, forKey: initialRefreshKey)
20 | // }
21 | // }
22 |
23 | // ... other settings properties ...
24 | }
25 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/App/GroupContainerURL.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | @MainActor
4 | struct GroupContainerURL {
5 | private static let fileManager: FileManager = FileManager.default
6 | static func groupContainerURL() -> URL? {
7 | return fileManager.containerURL(
8 | forSecurityApplicationGroupIdentifier:
9 | Identifiers.groupID)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/App/Identifiers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public enum Identifiers {
4 | static let groupID: String = "group.dev.arjuna.WebShield"
5 | }
6 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/App/WebShieldApp.swift:
--------------------------------------------------------------------------------
1 | import SwiftData
2 | import SwiftUI
3 |
4 | @main
5 | struct WebShieldApp: App {
6 | @StateObject private var dataManager = DataManager()
7 | @StateObject private var refreshErrorViewModel = RefreshErrorViewModel()
8 | @StateObject private var contentBlockerState: ContentBlockerState
9 | @StateObject private var advancedExtensionState = WebExtensionState()
10 |
11 | init() {
12 |
13 | let refreshErrorViewModel = RefreshErrorViewModel()
14 | _refreshErrorViewModel = StateObject(wrappedValue: refreshErrorViewModel)
15 | _contentBlockerState = StateObject(
16 | wrappedValue: ContentBlockerState(refreshErrorViewModel: refreshErrorViewModel))
17 | }
18 |
19 | var body: some Scene {
20 | WindowGroup {
21 | ContentView()
22 | .environmentObject(dataManager)
23 | .environmentObject(contentBlockerState)
24 | .environmentObject(advancedExtensionState)
25 | .environmentObject(refreshErrorViewModel)
26 | .modelContainer(dataManager.container)
27 | .onAppear {
28 | Task {
29 | await dataManager.seedDataIfNeeded()
30 | }
31 | }
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Extensions/ColorExtension.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | import Foundation
3 | import SwiftUI
4 |
5 | extension Color {
6 | static let background = Color(NSColor.windowBackgroundColor)
7 | static let secondaryBackground = Color(NSColor.underPageBackgroundColor)
8 | static let tertiaryBackground = Color(NSColor.controlBackgroundColor)
9 | }
10 | #endif
11 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Extensions/ModelContextExtension.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftData
3 |
4 | extension ModelContext {
5 | func deleteAll(_ type: T.Type) throws {
6 | let fetchDescriptor = FetchDescriptor()
7 | let results = try fetch(fetchDescriptor)
8 | for object in results {
9 | delete(object)
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/FilterList.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftData
3 |
4 | // MARK: - Model
5 | @Model
6 | final class FilterList: Identifiable, Hashable {
7 | @Attribute(.unique) var id: String
8 | var name: String = "No Name"
9 | var version: String = "N/A"
10 | var desc: String = "No Description"
11 | var categoryString: String = FilterListCategory.custom.rawValue
12 | var isEnabled: Bool = false
13 | var order: Int = 0
14 | var downloadUrl: String? = "https://example.com"
15 | var homepageUrl: String? = "https://example.com"
16 | var standardRuleCount: Int = 0
17 | var advancedRuleCount: Int = 0
18 | var lastUpdated: Date = Date()
19 | var informationUrl: String? = "https://example.com"
20 | var downloaded: Bool = false
21 | var needsRefresh: Bool = true
22 |
23 | // Initializer
24 | init(
25 | name: String = "No Name",
26 | version: String = "No Version",
27 | desc: String = "No Description",
28 | category: FilterListCategory,
29 | isEnabled: Bool = false,
30 | order: Int = 0,
31 | downloadUrl: String? = nil,
32 | homepageUrl: String? = nil,
33 | informationUrl: String? = nil,
34 | downloaded: Bool = false,
35 | needsRefresh: Bool = true
36 | ) {
37 | self.id = UUID().uuidString
38 | self.name = name
39 | self.version = version
40 | self.desc = desc
41 | self.categoryString = category.rawValue
42 | self.isEnabled = isEnabled
43 | self.order = order
44 | self.downloadUrl = downloadUrl
45 | self.homepageUrl = homepageUrl
46 | self.informationUrl = informationUrl
47 | self.downloaded = false
48 | self.needsRefresh = true
49 | }
50 |
51 | static func == (lhs: FilterList, rhs: FilterList) -> Bool {
52 | lhs.id == rhs.id
53 | }
54 |
55 | func hash(into hasher: inout Hasher) {
56 | hasher.combine(id)
57 | }
58 |
59 | var category: FilterListCategory? {
60 | FilterListCategory(rawValue: categoryString)
61 | }
62 |
63 | var totalRuleCount: Int {
64 | standardRuleCount + advancedRuleCount
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/FilterListCategory.swift:
--------------------------------------------------------------------------------
1 | // WebShieldApp/Source/Models/FilterListCategory.swift
2 | import SwiftUI
3 |
4 | // Add Codable conformance
5 | enum FilterListCategory: String, CaseIterable, Identifiable, Codable {
6 | case enabled = "Enabled"
7 | case all = "All"
8 | case ads = "Ads"
9 | case privacy = "Privacy"
10 | case security = "Security"
11 | case multipurpose = "Multipurpose"
12 | case cookies = "Cookies"
13 | case social = "Social"
14 | case annoyances = "Annoyances"
15 | case regional = "Regional"
16 | case experimental = "Experimental"
17 | case custom = "Custom"
18 |
19 | var id: String { self.rawValue }
20 |
21 | var systemImage: String {
22 | switch self {
23 | case .enabled: return "checkmark.circle.fill"
24 | case .all: return "square.grid.2x2"
25 | case .ads: return "megaphone"
26 | case .privacy: return "hand.raised"
27 | case .security: return "lock"
28 | case .multipurpose: return "square.stack.3d.down.right"
29 | case .cookies: return "circle.dotted.circle"
30 | case .social: return "bubble.left.and.bubble.right"
31 | case .annoyances: return "exclamationmark.triangle"
32 | case .regional: return "globe"
33 | case .experimental: return "flask"
34 | case .custom: return "slider.horizontal.3"
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/FilterListData.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct FilterListData {
4 | let id: String
5 | let name: String
6 | let downloadUrl: String
7 | let category: FilterListCategory
8 | let isSelected: Bool
9 | let description: String
10 | let homepageUrl: String?
11 | let informationUrl: String?
12 |
13 | init(
14 | id: String,
15 | name: String,
16 | downloadUrl: String,
17 | category: FilterListCategory,
18 | isSelected: Bool,
19 | description: String,
20 | homepageUrl: String? = nil,
21 | informationUrl: String? = nil
22 | ) {
23 | self.id = id
24 | self.name = name
25 | self.downloadUrl = downloadUrl
26 | self.category = category
27 | self.isSelected = isSelected
28 | self.description = description
29 | self.homepageUrl = homepageUrl
30 | self.informationUrl = informationUrl
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/FilterListError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum FilterListError: LocalizedError {
4 | case invalidData
5 | case invalidFormat
6 | case downloadFailed
7 | case parsingFailed
8 | case invalidURL
9 | case invalidServerResponse
10 |
11 | var errorDescription: String? {
12 | switch self {
13 | case .invalidURL:
14 | return "The provided URL is not valid."
15 | case .invalidData:
16 | return "Invalid filter list data"
17 | case .invalidFormat:
18 | return "Invalid filter list format"
19 | case .downloadFailed:
20 | return "Failed to download filter list"
21 | case .parsingFailed:
22 | return "Failed to parse filter list"
23 | case .invalidServerResponse:
24 | return "Failed to receive filter list from URL due to invalid server response"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/FilterListSection.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct FilterListSection: Identifiable {
4 | let title: String
5 | let filterLists: [FilterList]
6 | let category: FilterListCategory
7 | var id: String { title }
8 | }
9 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/ParsedMetadata.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// A simple struct to hold metadata parsed from filter list text.
4 | struct ParsedMetadata {
5 | var version: String
6 | var homepage: String?
7 | }
8 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/ProcessedConversionResult.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | /// Simple container for the results of a single filter list conversion
4 | struct ProcessedConversionResult {
5 | /// The regular JSON string (Safari content blocker format)
6 | let converted: String?
7 | /// The advanced blocking JSON string (if any)
8 | let advancedBlocking: String?
9 | /// Count of standard rules
10 | let convertedCount: Int
11 | /// Count of advanced rules
12 | let advancedBlockingCount: Int
13 | /// Number of errors encountered
14 | let errorsCount: Int
15 | /// If the converter found the rules were over the size limit
16 | let overLimit: Bool
17 | /// A human-readable message about the conversion
18 | let message: String?
19 | }
20 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/RefreshError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct RefreshError: Identifiable, Equatable { // Equatable to remove duplicates
4 | let id = UUID() // Automatic unique identifier
5 | let title: String
6 | let message: String
7 | let timestamp: Date
8 |
9 | // Static method for creating localized error descriptions
10 | static func localizedError(for error: Error, in listName: String) -> RefreshError {
11 | let title = "Error in \(listName)"
12 | let message: String
13 |
14 | switch error {
15 | case FilterListError.invalidURL:
16 | message = NSLocalizedString("The provided URL is not valid.", comment: "Invalid URL error")
17 | case FilterListError.invalidData:
18 | message = NSLocalizedString("Invalid filter list data.", comment: "Invalid data error")
19 | case FilterListError.invalidFormat:
20 | message = NSLocalizedString("Invalid filter list format.", comment: "Invalid format error")
21 | case FilterListError.downloadFailed:
22 | message = NSLocalizedString("Failed to download filter list.", comment: "Download failed error")
23 | case FilterListError.parsingFailed:
24 | message = NSLocalizedString("Failed to parse filter list.", comment: "Parsing failed error")
25 | case FilterListError.invalidServerResponse:
26 | message = NSLocalizedString(
27 | "Failed to receive filter list due to invalid server response.",
28 | comment: "Invalid server response error")
29 | default:
30 | message = NSLocalizedString("An unexpected error occurred.", comment: "Unexpected error")
31 | }
32 |
33 | return RefreshError(title: title, message: message, timestamp: Date())
34 | }
35 |
36 | static func == (lhs: RefreshError, rhs: RefreshError) -> Bool {
37 | lhs.title == rhs.title && lhs.message == rhs.message
38 | }
39 |
40 | // Consider adding an initializer from NSError if needed
41 | }
42 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/RefreshState.swift:
--------------------------------------------------------------------------------
1 | enum RefreshState {
2 | case idle
3 | case refreshing
4 | case success
5 | case failed([RefreshError]) // Store the errors if it failed
6 | }
7 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/TotalStats.swift:
--------------------------------------------------------------------------------
1 | public struct TotalStats {
2 | var totalConvertedCount: Int = 0
3 | var convertedCount: Int = 0
4 | var errorsCount: Int = 0
5 | var overLimit: Int = 0
6 | }
7 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Models/TrustedSite.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftData
3 | import SwiftUI
4 |
5 | @Model
6 | final class TrustedSite {
7 | @Attribute(.unique) var id: String
8 | var domain: String
9 |
10 | init(domain: String) {
11 | self.id = UUID().uuidString
12 | self.domain = domain
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Services/ContentBlockerState.swift:
--------------------------------------------------------------------------------
1 | @preconcurrency import SafariServices
2 | import SwiftUI
3 |
4 | @MainActor
5 | final class ContentBlockerState: ObservableObject {
6 | var refreshErrorViewModel: RefreshErrorViewModel
7 |
8 | init(refreshErrorViewModel: RefreshErrorViewModel) {
9 | self.refreshErrorViewModel = refreshErrorViewModel
10 | }
11 |
12 | // Reload content blocker for a specific category
13 | func reloadContentBlocker(for category: FilterListCategory) async throws {
14 | await WebShieldLogger.shared.log("🔄 Starting reload for \(category.rawValue)")
15 | // Skip reloading for the "all" category
16 | guard category != .all else { return }
17 |
18 | let identifier = "dev.arjuna.WebShield.DeclarativeBlockList-\(category.rawValue)"
19 | do {
20 | try await SFContentBlockerManager.reloadContentBlocker(withIdentifier: identifier)
21 | await WebShieldLogger.shared.log("Content blocker reloaded successfully for category: \(category.rawValue)")
22 | } catch {
23 | await handleReloadError(error, for: category)
24 | }
25 | }
26 |
27 | // Add a new method to report errors
28 | private func reportError(title: String, message: String) {
29 | let error = RefreshError(title: title, message: message, timestamp: Date())
30 | refreshErrorViewModel.addError(error)
31 | }
32 |
33 | // Handle errors, now with category
34 | private func handleReloadError(_ error: Error, for category: FilterListCategory) async {
35 | let nsError = error as NSError
36 | await WebShieldLogger.shared.log("ERROR: Failed to reload content blocker for category: \(category.rawValue)")
37 | await WebShieldLogger.shared.log("Error description: \(nsError.localizedDescription)")
38 | await WebShieldLogger.shared.log("Error domain: \(nsError.domain)")
39 | await WebShieldLogger.shared.log("Error code: \(nsError.code)")
40 |
41 | if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? NSError {
42 | await WebShieldLogger.shared.log("Underlying error: \(underlyingError)")
43 | }
44 |
45 | await WebShieldLogger.shared.log("User Info:")
46 | for (key, value) in nsError.userInfo {
47 | await WebShieldLogger.shared.log(" \(key): \(value)")
48 | }
49 |
50 | if nsError.domain == "SFErrorDomain" {
51 | await handleSFErrorDomain(code: nsError.code, for: category)
52 | } else {
53 | // Handle other error domains if needed
54 | reportError(
55 | title: "Error Reloading \(category.rawValue)",
56 | message: "Error \(nsError.code): \(nsError.localizedDescription)")
57 | }
58 | }
59 |
60 | // MARK: - NEW: Check if a content blocker is enabled for a category
61 | func isContentBlockerEnabled(for category: FilterListCategory) async -> Bool {
62 | // If category is .all, we might treat it as "not applicable"
63 | guard category != .all else { return true }
64 |
65 | let identifier = "dev.arjuna.WebShield.DeclarativeBlockList-\(category.rawValue)"
66 |
67 | // Use a continuation to convert completion-handler to async/await
68 | do {
69 | return try await SFContentBlockerManager.stateOfContentBlocker(withIdentifier: identifier).isEnabled
70 | } catch {
71 | return false
72 | }
73 | }
74 |
75 | // Handle SFErrorDomain errors, now with category
76 | private func handleSFErrorDomain(code: Int, for category: FilterListCategory) async {
77 | let identifier = "dev.arjuna.WebShield.DeclarativeBlockList-\(category.rawValue)"
78 | let title = "Content Blocker Error (\(category.rawValue))"
79 | let message: String
80 |
81 | switch code {
82 | case 1:
83 | message = "Content Blocker not found. Check the identifier: \(identifier)"
84 | await WebShieldLogger.shared.log(
85 | "SFErrorDomain error 1: Content Blocker not found or not owned by you."
86 | )
87 | await WebShieldLogger.shared.log("Bundle Identifier: \(identifier)")
88 | await WebShieldLogger.shared.log(
89 | "Please check JSON validity and file size (max 6MB, 150,000 rules)."
90 | )
91 | case 2:
92 | message = "No attachment found in NSExtensionItem."
93 | await WebShieldLogger.shared.log("SFErrorDomain error 2: NSExtensionItem missing attachment.")
94 | case 3:
95 | message = "Error loading content blocker extension."
96 | await WebShieldLogger.shared.log(
97 | "SFErrorDomain error 3: Error loading content blocker extension."
98 | )
99 | default:
100 | message = "Unknown SFErrorDomain error code: \(code)"
101 | await WebShieldLogger.shared.log("Unknown SFErrorDomain error code: \(code)")
102 | }
103 |
104 | reportError(title: title, message: message)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Services/WebExtensionState.swift:
--------------------------------------------------------------------------------
1 | import SafariServices
2 |
3 | @MainActor
4 | final class WebExtensionState: ObservableObject {
5 | private let advancedExtensionIdentifier = "dev.arjuna.WebShield.Advanced"
6 |
7 | func isAdvancedExtensionEnabled() async -> Bool {
8 | #if os(macOS)
9 | do {
10 | return try await SFSafariExtensionManager.stateOfSafariExtension(
11 | withIdentifier: advancedExtensionIdentifier
12 | ).isEnabled
13 | } catch {
14 | return false
15 | }
16 | #elseif os(iOS) || os(visionOS)
17 | return true
18 | #endif
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Services/WebShieldLogger.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | /// A simple, concurrency-safe logger for in-app text logs.
5 | public actor WebShieldLogger {
6 |
7 | /// Keep a ring buffer or simple array for logs.
8 | /// Here we just append and trim to the last 1000 entries.
9 | private var logs: [String] = []
10 |
11 | /// Single shared instance (a “global” with minimal side effects).
12 | public static let shared = WebShieldLogger()
13 |
14 | /// Limit the maximum number of lines we store in memory.
15 | private let maxEntries = 1000
16 |
17 | /// Private initializer to enforce singleton usage.
18 | private init() {}
19 |
20 | // MARK: - Public API
21 |
22 | /// Appends a new line to the in-memory logs (and trims older entries).
23 | /// Also prints to the console for convenience.
24 | public func log(_ message: String) {
25 | let timestamp = Self.currentTimestamp()
26 | logs.append("[\(timestamp)] \(message)")
27 |
28 | // Keep memory usage bounded to maxEntries
29 | if logs.count > maxEntries {
30 | logs.removeFirst(logs.count - maxEntries)
31 | }
32 |
33 | // For development convenience, also log to console
34 | print(message)
35 | }
36 |
37 | /// Retrieves all logged lines.
38 | /// - Returns: An array of all log messages in chronological order.
39 | public func allLogs() -> [String] {
40 | logs
41 | }
42 |
43 | public func clearLogs() {
44 | logs.removeAll()
45 | log("Logs cleared.") // Optionally, log the action itself
46 | }
47 |
48 | // MARK: - Specialized Logging Methods
49 |
50 | /// Logs a simple timestamped step message (like “Processing,” etc.).
51 | public func logFilterListProcessingStep(_ step: String, for listName: String) {
52 | self.log("\(listName): \(step)")
53 | }
54 |
55 | /// Logs the start of a refresh event with a blank line for clarity.
56 | public func logRefreshStart() {
57 | self.log("\n\nSTARTING REFRESH\n")
58 | }
59 |
60 | /// Logs general “conversion statistics,” similar to your original code.
61 | public func logConversionStatistics(
62 | totalConvertedCount: Int,
63 | convertedCount: Int,
64 | errorsCount: Int,
65 | overLimit: Bool,
66 | for listName: String
67 | ) {
68 | self.log(
69 | """
70 | Conversion statistics for \(listName):
71 | - Total converted count: \(totalConvertedCount)
72 | - Converted count: \(convertedCount)
73 | - Errors count: \(errorsCount)
74 | - Over limit: \(overLimit)
75 | """)
76 | }
77 |
78 | /// Logs aggregated stats across multiple lists or processes.
79 | public func logTotalStatistics(_ stats: TotalStats) {
80 | self.log(
81 | """
82 | Total conversion statistics:
83 | - Total converted count: \(stats.totalConvertedCount)
84 | - Converted count: \(stats.convertedCount)
85 | - Errors count: \(stats.errorsCount)
86 | - Lists over limit: \(stats.overLimit)
87 | """)
88 | }
89 |
90 | // MARK: - Helper
91 |
92 | /// Generates a time-only (HH:mm:ss) localized timestamp.
93 | private static func currentTimestamp() -> String {
94 | let now = Date()
95 | return DateFormatter.localizedString(from: now, dateStyle: .none, timeStyle: .medium)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/ViewModels/ExtensionCheckViewModel.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @MainActor
4 | final class ExtensionCheckViewModel: ObservableObject {
5 | // Dependencies
6 | private let contentBlockerState: ContentBlockerState
7 | private let advancedExtensionState: WebExtensionState
8 | // Possibly another actor for advanced extension checks
9 |
10 | // Published state for UI
11 | @Published var missingExtensions: [String] = []
12 | @Published var showEnablePrompt = false
13 |
14 | init(contentBlockerState: ContentBlockerState, advancedExtensionState: WebExtensionState) {
15 | self.contentBlockerState = contentBlockerState
16 | self.advancedExtensionState = advancedExtensionState
17 | }
18 |
19 | func checkExtensions() async {
20 | missingExtensions.removeAll()
21 | showEnablePrompt = false
22 |
23 | // Check all content blockers
24 | for category in FilterListCategory.allCases where category != .all && category != .enabled {
25 | let isEnabled = await contentBlockerState.isContentBlockerEnabled(for: category)
26 | await WebShieldLogger.shared.log("Content Blocker for \(category.rawValue) is enabled: \(isEnabled)")
27 | if !isEnabled {
28 | missingExtensions.append("WebShield \(category.rawValue)")
29 | }
30 | }
31 |
32 | // Check advanced extension
33 | let advancedEnabled = await advancedExtensionState.isAdvancedExtensionEnabled()
34 | await WebShieldLogger.shared.log("Advanced (Web)Extension is enabled: \(advancedEnabled)")
35 | if !advancedEnabled {
36 | missingExtensions.append("WebShield Advanced")
37 | }
38 |
39 | // Show prompt only if something’s disabled
40 | showEnablePrompt = !missingExtensions.isEmpty
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/ViewModels/RefreshErrorViewModel.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | @MainActor
4 | class RefreshErrorViewModel: ObservableObject {
5 | @Published var errors: [RefreshError] = []
6 | @Published var showErrorView: Bool = false // Flag to show/hide the ErrorView
7 |
8 | // Clear errors
9 | func clearErrors() {
10 | errors.removeAll()
11 | showErrorView = false // Also hide the view when clearing
12 | }
13 |
14 | // Add an error
15 | func addError(_ error: RefreshError) {
16 | if !errors.contains(error) {
17 | errors.append(error)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Views/ContentView/FilterListRow.swift:
--------------------------------------------------------------------------------
1 | import SwiftData
2 | import SwiftUI
3 |
4 | struct FilterListRow: View {
5 | let filterList: FilterList
6 | @Environment(\.openURL) private var openURL
7 | @Environment(\.modelContext) private var modelContext
8 |
9 | private var providerData: FilterListData? {
10 | FilterListProvider.filterListData.first { $0.name == filterList.name }
11 | }
12 |
13 | var body: some View {
14 | VStack(alignment: .leading, spacing: 8) {
15 | HStack(alignment: .center, spacing: 0) {
16 | Text(filterList.name)
17 | .font(.headline)
18 | Spacer()
19 | Toggle(
20 | "Enable Filter",
21 | isOn: Binding(
22 | get: { filterList.isEnabled },
23 | set: { newValue in
24 | filterList.isEnabled = newValue
25 | filterList.needsRefresh = true
26 | try? modelContext.save()
27 | }
28 | )
29 | )
30 | .toggleStyle(.switch)
31 | .labelsHidden()
32 | }
33 | actionButtons()
34 |
35 | Text(filterList.desc)
36 | .font(.subheadline)
37 | .foregroundStyle(.secondary)
38 |
39 | footerInfo()
40 |
41 | }
42 | .padding(.vertical, 8)
43 | }
44 |
45 | @ViewBuilder
46 | private func footerInfo() -> some View {
47 | if filterList.downloaded {
48 | Text("\(filterList.totalRuleCount) rules")
49 | .font(.footnote)
50 | .foregroundStyle(.secondary)
51 |
52 | Text("Version: \(filterList.version)")
53 | .font(.footnote)
54 | .foregroundStyle(.secondary)
55 |
56 | Text("Last Updated: \(filterList.lastUpdated, formatter: itemFormatter)")
57 | .font(.footnote)
58 | .foregroundStyle(.secondary)
59 | } else {
60 | Text("Never Downloaded!")
61 | .font(.footnote)
62 | .foregroundStyle(.secondary)
63 | Text("Never Updated!")
64 | .font(.footnote)
65 | .foregroundStyle(.secondary)
66 |
67 | }
68 | }
69 |
70 | @ViewBuilder
71 | private func actionButtons() -> some View {
72 | HStack(spacing: 8) {
73 | if let homepage = filterList.homepageUrl, let url = URL(string: homepage) {
74 | createButton(url: url, systemImage: "house", helpText: "Open Homepage")
75 | }
76 |
77 | if let downloadUrl = (providerData?.downloadUrl ?? filterList.downloadUrl), let url = URL(string: downloadUrl) {
78 | createButton(url: url, systemImage: "eye", helpText: "View Filter Source")
79 | }
80 |
81 | if let infoURL = providerData?.informationUrl, let url = URL(string: infoURL) {
82 | createButton(url: url, systemImage: "info.circle", helpText: "View Filter Information")
83 | }
84 | }
85 | }
86 |
87 | private let itemFormatter: DateFormatter = {
88 | let formatter = DateFormatter()
89 | formatter.dateStyle = .medium
90 | formatter.timeStyle = .short
91 | return formatter
92 | }()
93 |
94 | @ViewBuilder
95 | private func createButton(url: URL, systemImage: String, helpText: String) -> some View {
96 | Button {
97 | openURL(url)
98 | } label: {
99 | Label(helpText, systemImage: systemImage)
100 | .labelStyle(.iconOnly)
101 | // .foregroundStyle(.secondary)
102 | }
103 | .buttonStyle(.borderless)
104 | .help(helpText)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Views/ContentView/StatsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatsView.swift
3 | // WebShield
4 | //
5 | // Created by Arjun on 1/11/25.
6 | //
7 |
8 | import Foundation
9 | import SwiftData
10 | import SwiftUI
11 |
12 | struct StatsView: View {
13 | @Query(sort: \FilterList.order, order: .forward) private var filterLists: [FilterList]
14 | var filteredLists: [FilterList]
15 |
16 | var body: some View {
17 | Section {
18 | HStack {
19 | // Shield
20 | ZStack {
21 | RoundedRectangle(cornerRadius: 8)
22 | .fill(.blue.opacity(0.15))
23 | .frame(width: 36, height: 36)
24 |
25 | Image(systemName: "shield")
26 | .imageScale(.medium)
27 | .foregroundStyle(.blue)
28 | .fontWeight(.semibold)
29 | }
30 |
31 | Grid(horizontalSpacing: 10, verticalSpacing: 4) {
32 | GridRow {
33 | Text("\(enabledCount)")
34 | .font(.title2)
35 | .bold()
36 | .gridColumnAlignment(.leading)
37 | Text("\(totalRules)")
38 | .font(.title2)
39 | .bold()
40 | .gridColumnAlignment(.leading)
41 | }
42 | GridRow {
43 | Text("Enabled")
44 | .font(.subheadline)
45 | .foregroundStyle(.secondary)
46 | .gridColumnAlignment(.leading)
47 | Text("Rules")
48 | .font(.subheadline)
49 | .foregroundStyle(.secondary)
50 | .gridColumnAlignment(.leading)
51 | }
52 | }
53 | .padding()
54 |
55 | Spacer()
56 | }
57 | }
58 |
59 | }
60 |
61 | private var enabledCount: Int {
62 | filteredLists.filter { $0.isEnabled }.count
63 | }
64 |
65 | private var totalRules: Int {
66 | filteredLists.filter { $0.isEnabled }.reduce(0) {
67 | $0 + $1.totalRuleCount
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Views/Error/ErrorView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct ErrorView: View {
4 | @EnvironmentObject var refreshErrorViewModel: RefreshErrorViewModel
5 | @Environment(\.dismiss) var dismiss
6 |
7 | var body: some View {
8 | NavigationStack {
9 | VStack(spacing: 20) {
10 | Image(systemName: "exclamationmark.triangle.fill")
11 | .font(.system(size: 60, weight: .regular))
12 | .foregroundColor(.orange)
13 | .padding(.top, 32)
14 | .symbolEffect(.bounce, value: refreshErrorViewModel.errors)
15 |
16 | VStack(spacing: 8) {
17 | Text("Refresh Failed")
18 | .font(.largeTitle)
19 | .fontWeight(.bold)
20 | .multilineTextAlignment(.center)
21 |
22 | Text("Some filter lists could not be updated")
23 | .font(.headline)
24 | .foregroundColor(.secondary)
25 | .multilineTextAlignment(.center)
26 | }
27 |
28 | ScrollView {
29 | VStack(alignment: .leading, spacing: 12) {
30 | ForEach(refreshErrorViewModel.errors) { error in
31 | VStack(alignment: .leading, spacing: 4) {
32 | Text(error.title)
33 | .font(.headline)
34 | .fontWeight(.semibold)
35 | Text(error.message)
36 | .font(.subheadline)
37 | .foregroundColor(.secondary)
38 | }
39 | .padding(.vertical, 8)
40 | .frame(maxWidth: .infinity, alignment: .leading)
41 | }
42 | }
43 | .padding(.horizontal)
44 | }
45 |
46 | Spacer()
47 |
48 | Button(action: {
49 | refreshErrorViewModel.showErrorView = false
50 | dismiss()
51 | }) {
52 | Text("Try Again")
53 | .font(.headline)
54 | .frame(maxWidth: .infinity)
55 | .padding()
56 | }
57 | .buttonStyle(.borderedProminent)
58 | .padding(.horizontal)
59 | .padding(.bottom, 24)
60 | }
61 | .padding()
62 | .navigationTitle("Error Details")
63 | .toolbar {
64 | ToolbarItem(placement: .cancellationAction) {
65 | Button("Dismiss") {
66 | dismiss()
67 | }
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Views/Logs/LogsView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct LogsView: View {
4 | @Environment(\.dismiss) private var dismiss
5 | @State private var logEntries: [String] = []
6 |
7 | var body: some View {
8 | NavigationStack {
9 | VStack {
10 | if logEntries.isEmpty {
11 | emptyStateView
12 | } else {
13 | logsContentView
14 | }
15 | }
16 | .padding()
17 | .navigationTitle("Logs")
18 | .toolbar {
19 | ToolbarItem(placement: .automatic) {
20 | Button("Done") { dismiss() }
21 | }
22 | ToolbarItem(placement: .automatic) {
23 | Button(action: copyLogs) {
24 | Label("Copy Logs", systemImage: "doc.on.doc")
25 | }
26 | }
27 | ToolbarItem(placement: .automatic) {
28 | Button(action: {
29 | Task {
30 | await clearLogs()
31 | }
32 | }) {
33 | Label("Clear Logs", systemImage: "trash")
34 | }
35 | }
36 |
37 | }
38 | }
39 | .onAppear {
40 | Task {
41 | // Fetch logs from the actor
42 | self.logEntries = await WebShieldLogger.shared.allLogs()
43 | }
44 | }
45 | }
46 |
47 | private var emptyStateView: some View {
48 | VStack {
49 | Spacer()
50 | Text("No Logs Yet")
51 | .font(.body)
52 | .foregroundStyle(.secondary)
53 | Spacer()
54 | }
55 | .multilineTextAlignment(.center)
56 | }
57 |
58 | private var logsContentView: some View {
59 | ScrollView {
60 | Text(logEntries.joined(separator: "\n"))
61 | .font(.system(.body, design: .monospaced))
62 | .textSelection(.enabled)
63 | .padding()
64 | }
65 | }
66 |
67 | private func copyLogs() {
68 | let logsText = logEntries.joined(separator: "\n")
69 | #if os(macOS)
70 | NSPasteboard.general.clearContents()
71 | NSPasteboard.general.setString(logsText, forType: .string)
72 | #else
73 | UIPasteboard.general.string = logsText
74 | #endif
75 | }
76 |
77 | private func clearLogs() async {
78 | await WebShieldLogger.shared.clearLogs()
79 | logEntries = await WebShieldLogger.shared.allLogs()
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Views/Settings/SettingsView.swift:
--------------------------------------------------------------------------------
1 | import SwiftData
2 | import SwiftUI
3 |
4 | struct SettingsView: View {
5 | @Environment(\.modelContext) private var modelContext
6 | @Environment(\.dismiss) private var dismiss
7 | @EnvironmentObject var dataManager: DataManager
8 |
9 | var body: some View {
10 | NavigationStack {
11 | SettingsForm()
12 | .navigationTitle("Settings")
13 | .toolbar {
14 | ToolbarItem(placement: .automatic) {
15 | Button("Done") {
16 | dismiss()
17 | }
18 | .buttonStyle(.automatic)
19 | }
20 | }
21 | }
22 | }
23 | }
24 |
25 | // MARK: - Settings Form
26 | struct SettingsForm: View {
27 | @EnvironmentObject var dataManager: DataManager
28 |
29 | var body: some View {
30 | Form {
31 | developerSection()
32 | }
33 | .formStyle(.grouped) // Grouped style is native on iOS/macOS
34 | }
35 |
36 | // MARK: - Developer Section
37 | private func developerSection() -> some View {
38 | Section(header: Text("Developer").font(.headline)) {
39 | Button(role: .destructive) {
40 | Task {
41 | await dataManager.resetModel()
42 | }
43 | } label: {
44 | Label("Reset Model", systemImage: "arrow.counterclockwise.circle.fill")
45 | }
46 | .accessibilityLabel("Reset the application model")
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Views/Shared/PulsatingCircleButton.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import SwiftData
3 | import SwiftUI
4 |
5 | struct PulsatingCircleButton: View {
6 |
7 | enum ButtonState {
8 | case upToDate
9 | case needsUpdate
10 | case notEnabled
11 | }
12 |
13 | enum FilterState {
14 | case needsDownload // Red
15 | case needsUpdate // Orange
16 | case downloaded // Green
17 | }
18 |
19 | @State private var isPulsating = false
20 | @Query(sort: \FilterList.order, order: .forward) private var filterLists: [FilterList]
21 |
22 | var body: some View {
23 | VStack {
24 | // Pulsating Circle
25 | ZStack {
26 | Circle()
27 | .fill(colorForState(currentFilterState()))
28 | .frame(width: 4, height: 4)
29 | .scaleEffect(isPulsating ? 1.2 : 1.0)
30 | .animation(
31 | .easeInOut(duration: 1)
32 | .repeatForever(autoreverses: true),
33 | value: isPulsating
34 | )
35 | .onAppear {
36 | isPulsating = true
37 | }
38 |
39 | Circle()
40 | .stroke(
41 | colorForState(currentFilterState()).opacity(0.6),
42 | lineWidth: 8
43 | )
44 | .frame(width: 2, height: 2)
45 | .scaleEffect(isPulsating ? 1.4 : 1.0)
46 | .opacity(0.5)
47 | .animation(
48 | .easeInOut(duration: 1)
49 | .repeatForever(autoreverses: true),
50 | value: isPulsating
51 | )
52 | .onAppear {
53 | isPulsating = true
54 | }
55 | }
56 | }
57 | }
58 |
59 | // MARK: - Helper Functions
60 |
61 | private func colorForState(_ state: FilterState) -> Color {
62 | switch state {
63 | case .needsDownload:
64 | return .red
65 | case .needsUpdate:
66 | return .orange
67 | case .downloaded:
68 | return .green
69 | }
70 | }
71 |
72 | private func currentFilterState() -> FilterState {
73 | // Determine the state based on the filter lists
74 | let oneToggledNotDownloaded = filterLists.contains { $0.isEnabled && !$0.downloaded }
75 | let oneDownloadedNotToggled = filterLists.contains { !$0.isEnabled && $0.downloaded }
76 | let allDownloaded = filterLists.allSatisfy { $0.downloaded }
77 | let noneDownloaded = filterLists.allSatisfy { !$0.downloaded }
78 |
79 | if noneDownloaded {
80 | return .needsDownload // Red
81 | } else if oneToggledNotDownloaded || oneDownloadedNotToggled {
82 | return .needsUpdate // Orange
83 | } else if allDownloaded {
84 | return .downloaded // Green
85 | } else {
86 | return .downloaded // Default to green
87 | }
88 |
89 | // TODO
90 | // let hasPerformedRefresh = AppSettings.shared.hasPerformedInitialRefresh
91 | // let lastRefreshedEnabledFilters = AppSettings.shared.lastRefreshedEnabledFilters
92 | // let currentEnabledFilters =
93 | // filterLists
94 | // .filter { $0.isEnabled }
95 | // .map { $0.id }
96 | // .reduce(into: Set()) { $0.insert($1) }
97 | // let anyListDownloaded = filterLists.contains(where: { $0.downloaded })
98 | // let anyListEnabled = filterLists.contains(where: { $0.isEnabled })
99 | // let anyEnabledListNeedsDownload = filterLists.contains(where: { $0.isEnabled && !$0.downloaded })
100 | // let anyEnabledListNeedsRefresh = filterLists.contains(where: { $0.isEnabled && $0.needsRefresh })
101 | //
102 | // // 1. RED: If no lists are downloaded, none are enabled, OR an initial refresh has never happened
103 | // if !anyListDownloaded || !anyListEnabled || !hasPerformedRefresh {
104 | // return .needsDownload
105 | // }
106 | //
107 | // // 2. ORANGE: Needs Update if:
108 | // // - Any enabled list needs downloading OR
109 | // // - Any enabled list needs a refresh OR
110 | // // - The set of currently enabled filters is different from the last refreshed set
111 | // if anyEnabledListNeedsDownload || anyEnabledListNeedsRefresh
112 | // || currentEnabledFilters != lastRefreshedEnabledFilters
113 | // {
114 | // return .needsUpdate
115 | // }
116 | //
117 | // // 3. GREEN: If all enabled lists are downloaded, none need a refresh, and the enabled filter configuration hasn't changed since the last refresh.
118 | // return .downloaded
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/WebShieldApp/Source/Views/Sidebar/SidebarView.swift:
--------------------------------------------------------------------------------
1 | // WebShieldApp/Source/Views/Sidebar/SidebarView.swift
2 | import SwiftData
3 | import SwiftUI
4 |
5 | struct SidebarView: View {
6 | @Binding var selectedCategory: FilterListCategory?
7 |
8 | var body: some View {
9 | List(selection: $selectedCategory) {
10 | // Consider putting "Enabled" in its own section for clarity
11 | Section(header: Text("Status")) { // Optional: New Section Header
12 | NavigationLink(value: FilterListCategory.enabled) {
13 | Label(
14 | FilterListCategory.enabled.rawValue,
15 | systemImage: FilterListCategory.enabled.systemImage
16 | )
17 | }
18 | }
19 |
20 | Section(header: Text("Categories")) {
21 | // Original categories (excluding .enabled if placed above)
22 | let categories: [FilterListCategory] = [
23 | .all, .ads, .privacy, .security, .multipurpose, .cookies, .social, .annoyances, .regional,
24 | .experimental, .custom,
25 | ]
26 | ForEach(categories, id: \.self) { category in
27 | NavigationLink(value: category) {
28 | Label(
29 | category.rawValue,
30 | systemImage: category.systemImage
31 | )
32 | }
33 | }
34 | }
35 | }
36 | .navigationTitle("WebShield")
37 | // Optional: Set default selection if needed
38 | .onAppear {
39 | if selectedCategory == nil {
40 | selectedCategory = .all // Or .all, etc.
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/WebShieldApp/WebShieldApp.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.application-groups
8 |
9 | group.dev.arjuna.WebShield
10 |
11 | com.apple.security.files.user-selected.read-only
12 |
13 | com.apple.security.network.client
14 |
15 | com.apple.security.network.server
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/WebShieldCookies/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Cookies" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldCookies/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Cookies", category: "ContentBlockerRequestHandler")
6 | private let specificList = "cookies.json"
7 |
8 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
9 | func beginRequest(with context: NSExtensionContext) {
10 | let requiredPart = "group.dev.arjuna.WebShield"
11 |
12 | logger.log("Content Blocker logging...")
13 |
14 | guard
15 | let containerURL = FileManager.default.containerURL(
16 | forSecurityApplicationGroupIdentifier: requiredPart)
17 | else {
18 | logger.log(
19 | "Error: Could not get container URL for group \(requiredPart)")
20 | context.completeRequest(returningItems: nil, completionHandler: nil)
21 | return
22 | }
23 |
24 | logger.log("Successfully got container URL for group \(requiredPart)")
25 |
26 | let blockerlistURL = containerURL.appendingPathComponent(
27 | "\(specificList)")
28 |
29 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
30 | logger.log("Content Blocker Error: \(specificList) does not exist")
31 | context.completeRequest(returningItems: nil, completionHandler: nil)
32 | return
33 | }
34 |
35 | logger.log("\(specificList) exists")
36 |
37 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
38 | logger.log("Sending attachment")
39 | let item = NSExtensionItem()
40 | item.attachments = [attachment]
41 | context.completeRequest(
42 | returningItems: [item], completionHandler: nil)
43 | logger.log("Completed request")
44 | } else {
45 | logger.log(
46 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
47 | context.completeRequest(returningItems: nil, completionHandler: nil)
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WebShieldCookies/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldCustom/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Custom" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldCustom/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Custom", category: "ContentBlockerRequestHandler")
6 | private let specificList = "custom.json"
7 |
8 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
9 | func beginRequest(with context: NSExtensionContext) {
10 | let requiredPart = "group.dev.arjuna.WebShield"
11 |
12 | logger.log("Content Blocker logging...")
13 |
14 | guard
15 | let containerURL = FileManager.default.containerURL(
16 | forSecurityApplicationGroupIdentifier: requiredPart)
17 | else {
18 | logger.log(
19 | "Error: Could not get container URL for group \(requiredPart)")
20 | context.completeRequest(returningItems: nil, completionHandler: nil)
21 | return
22 | }
23 |
24 | logger.log("Successfully got container URL for group \(requiredPart)")
25 |
26 | let blockerlistURL = containerURL.appendingPathComponent(
27 | "\(specificList)")
28 |
29 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
30 | logger.log("Content Blocker Error: \(specificList) does not exist")
31 | context.completeRequest(returningItems: nil, completionHandler: nil)
32 | return
33 | }
34 |
35 | logger.log("\(specificList) exists")
36 |
37 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
38 | logger.log("Sending attachment")
39 | let item = NSExtensionItem()
40 | item.attachments = [attachment]
41 | context.completeRequest(
42 | returningItems: [item], completionHandler: nil)
43 | logger.log("Completed request")
44 | } else {
45 | logger.log(
46 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
47 | context.completeRequest(returningItems: nil, completionHandler: nil)
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WebShieldCustom/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldExperimental/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Experimental" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldExperimental/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Experimental", category: "ContentBlockerRequestHandler")
6 | private let specificList = "experimental.json"
7 |
8 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
9 | func beginRequest(with context: NSExtensionContext) {
10 | let requiredPart = "group.dev.arjuna.WebShield"
11 |
12 | logger.log("Content Blocker logging...")
13 |
14 | guard
15 | let containerURL = FileManager.default.containerURL(
16 | forSecurityApplicationGroupIdentifier: requiredPart)
17 | else {
18 | logger.log(
19 | "Error: Could not get container URL for group \(requiredPart)")
20 | context.completeRequest(returningItems: nil, completionHandler: nil)
21 | return
22 | }
23 |
24 | logger.log("Successfully got container URL for group \(requiredPart)")
25 |
26 | let blockerlistURL = containerURL.appendingPathComponent(
27 | "\(specificList)")
28 |
29 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
30 | logger.log("Content Blocker Error: \(specificList) does not exist")
31 | context.completeRequest(returningItems: nil, completionHandler: nil)
32 | return
33 | }
34 |
35 | logger.log("\(specificList) exists")
36 |
37 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
38 | logger.log("Sending attachment")
39 | let item = NSExtensionItem()
40 | item.attachments = [attachment]
41 | context.completeRequest(
42 | returningItems: [item], completionHandler: nil)
43 | logger.log("Completed request")
44 | } else {
45 | logger.log(
46 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
47 | context.completeRequest(returningItems: nil, completionHandler: nil)
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WebShieldExperimental/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldMultipurpose/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Multipurpose" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldMultipurpose/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Multipurpose", category: "ContentBlockerRequestHandler")
6 | private let specificList = "multipurpose.json"
7 |
8 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
9 | func beginRequest(with context: NSExtensionContext) {
10 | let requiredPart = "group.dev.arjuna.WebShield"
11 |
12 | logger.log("Content Blocker logging...")
13 |
14 | guard
15 | let containerURL = FileManager.default.containerURL(
16 | forSecurityApplicationGroupIdentifier: requiredPart)
17 | else {
18 | logger.log(
19 | "Error: Could not get container URL for group \(requiredPart)")
20 | context.completeRequest(returningItems: nil, completionHandler: nil)
21 | return
22 | }
23 |
24 | logger.log("Successfully got container URL for group \(requiredPart)")
25 |
26 | let blockerlistURL = containerURL.appendingPathComponent(
27 | "\(specificList)")
28 |
29 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
30 | logger.log("Content Blocker Error: \(specificList) does not exist")
31 | context.completeRequest(returningItems: nil, completionHandler: nil)
32 | return
33 | }
34 |
35 | logger.log("\(specificList) exists")
36 |
37 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
38 | logger.log("Sending attachment")
39 | let item = NSExtensionItem()
40 | item.attachments = [attachment]
41 | context.completeRequest(
42 | returningItems: [item], completionHandler: nil)
43 | logger.log("Completed request")
44 | } else {
45 | logger.log(
46 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
47 | context.completeRequest(returningItems: nil, completionHandler: nil)
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WebShieldMultipurpose/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldPrivacy/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Privacy" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldPrivacy/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Privacy", category: "ContentBlockerRequestHandler")
6 |
7 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
8 | func beginRequest(with context: NSExtensionContext) {
9 | let requiredPart = "group.dev.arjuna.WebShield"
10 |
11 | logger.log("Content Blocker logging...")
12 |
13 | guard
14 | let containerURL = FileManager.default.containerURL(
15 | forSecurityApplicationGroupIdentifier: requiredPart)
16 | else {
17 | logger.log(
18 | "Error: Could not get container URL for group \(requiredPart)")
19 | context.completeRequest(returningItems: nil, completionHandler: nil)
20 | return
21 | }
22 |
23 | logger.log("Successfully got container URL for group \(requiredPart)")
24 |
25 | let blockerlistURL = containerURL.appendingPathComponent(
26 | "privacy.json")
27 |
28 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
29 | logger.log("Content Blocker Error: privacy.json does not exist")
30 | context.completeRequest(returningItems: nil, completionHandler: nil)
31 | return
32 | }
33 |
34 | logger.log("privacy.json exists")
35 |
36 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
37 | logger.log("Sending attachment")
38 | let item = NSExtensionItem()
39 | item.attachments = [attachment]
40 | context.completeRequest(
41 | returningItems: [item], completionHandler: nil)
42 | logger.log("Completed request")
43 | } else {
44 | logger.log(
45 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
46 | context.completeRequest(returningItems: nil, completionHandler: nil)
47 | }
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/WebShieldPrivacy/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldRegional/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Regional" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldRegional/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Regional", category: "ContentBlockerRequestHandler")
6 | private let specificList = "regional.json"
7 |
8 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
9 | func beginRequest(with context: NSExtensionContext) {
10 | let requiredPart = "group.dev.arjuna.WebShield"
11 |
12 | logger.log("Content Blocker logging...")
13 |
14 | guard
15 | let containerURL = FileManager.default.containerURL(
16 | forSecurityApplicationGroupIdentifier: requiredPart)
17 | else {
18 | logger.log(
19 | "Error: Could not get container URL for group \(requiredPart)")
20 | context.completeRequest(returningItems: nil, completionHandler: nil)
21 | return
22 | }
23 |
24 | logger.log("Successfully got container URL for group \(requiredPart)")
25 |
26 | let blockerlistURL = containerURL.appendingPathComponent(
27 | "\(specificList)")
28 |
29 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
30 | logger.log("Content Blocker Error: \(specificList) does not exist")
31 | context.completeRequest(returningItems: nil, completionHandler: nil)
32 | return
33 | }
34 |
35 | logger.log("\(specificList) exists")
36 |
37 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
38 | logger.log("Sending attachment")
39 | let item = NSExtensionItem()
40 | item.attachments = [attachment]
41 | context.completeRequest(
42 | returningItems: [item], completionHandler: nil)
43 | logger.log("Completed request")
44 | } else {
45 | logger.log(
46 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
47 | context.completeRequest(returningItems: nil, completionHandler: nil)
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WebShieldRegional/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldSecurity/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Security" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldSecurity/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Security", category: "ContentBlockerRequestHandler")
5 | private let specificList = "security.json"
6 |
7 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
8 | func beginRequest(with context: NSExtensionContext) {
9 | let requiredPart = "group.dev.arjuna.WebShield"
10 |
11 | logger.log("Content Blocker logging...")
12 |
13 | guard
14 | let containerURL = FileManager.default.containerURL(
15 | forSecurityApplicationGroupIdentifier: requiredPart)
16 | else {
17 | logger.log(
18 | "Error: Could not get container URL for group \(requiredPart)")
19 | context.completeRequest(returningItems: nil, completionHandler: nil)
20 | return
21 | }
22 |
23 | logger.log("Successfully got container URL for group \(requiredPart)")
24 |
25 | let blockerlistURL = containerURL.appendingPathComponent(
26 | "\(specificList)")
27 |
28 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
29 | logger.log("Content Blocker Error: \(specificList) does not exist")
30 | context.completeRequest(returningItems: nil, completionHandler: nil)
31 | return
32 | }
33 |
34 | logger.log("\(specificList) exists")
35 |
36 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
37 | logger.log("Sending attachment")
38 | let item = NSExtensionItem()
39 | item.attachments = [attachment]
40 | context.completeRequest(
41 | returningItems: [item], completionHandler: nil)
42 | logger.log("Completed request")
43 | } else {
44 | logger.log(
45 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
46 | context.completeRequest(returningItems: nil, completionHandler: nil)
47 | }
48 |
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/WebShieldSecurity/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------
/WebShieldSocial/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | $(APP_DISPLAY_NAME)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | ITSAppUsesNonExemptEncryption
24 |
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSExtension
28 |
29 | NSExtensionPointIdentifier
30 | com.apple.Safari.content-blocker
31 | NSExtensionPrincipalClass
32 | $(PRODUCT_MODULE_NAME).ContentBlockerRequestHandler
33 |
34 | NSHumanReadableDescription
35 | Contains "Social" category filters' blocking rules. All blocking is handled by Safari, so the app never sees your browsing activity.
36 |
37 |
38 |
--------------------------------------------------------------------------------
/WebShieldSocial/Source/ContentBlockerRequestHandler.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import os
3 |
4 | private let logger = Logger(
5 | subsystem: "dev.arjuna.WebShield.DeclarativeBlockList-Social", category: "ContentBlockerRequestHandler")
6 | private let specificList = "social.json"
7 |
8 | final class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
9 | func beginRequest(with context: NSExtensionContext) {
10 | let requiredPart = "group.dev.arjuna.WebShield"
11 |
12 | logger.log("Content Blocker logging...")
13 |
14 | guard
15 | let containerURL = FileManager.default.containerURL(
16 | forSecurityApplicationGroupIdentifier: requiredPart)
17 | else {
18 | logger.log(
19 | "Error: Could not get container URL for group \(requiredPart)")
20 | context.completeRequest(returningItems: nil, completionHandler: nil)
21 | return
22 | }
23 |
24 | logger.log("Successfully got container URL for group \(requiredPart)")
25 |
26 | let blockerlistURL = containerURL.appendingPathComponent(
27 | "\(specificList)")
28 |
29 | guard FileManager.default.fileExists(atPath: blockerlistURL.path) else {
30 | logger.log("Content Blocker Error: \(specificList) does not exist")
31 | context.completeRequest(returningItems: nil, completionHandler: nil)
32 | return
33 | }
34 |
35 | logger.log("\(specificList) exists")
36 |
37 | if let attachment = NSItemProvider(contentsOf: blockerlistURL) {
38 | logger.log("Sending attachment")
39 | let item = NSExtensionItem()
40 | item.attachments = [attachment]
41 | context.completeRequest(
42 | returningItems: [item], completionHandler: nil)
43 | logger.log("Completed request")
44 | } else {
45 | logger.log(
46 | "Error: Could not create NSItemProvider for \(blockerlistURL)")
47 | context.completeRequest(returningItems: nil, completionHandler: nil)
48 | }
49 |
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/WebShieldSocial/blockerList.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "action": {
4 | "type": "block"
5 | },
6 | "trigger": {
7 | "url-filter": "webkit.svg"
8 | }
9 | }
10 | ]
11 |
--------------------------------------------------------------------------------