├── .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 | 24 | 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 | --------------------------------------------------------------------------------