├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── documentation-improvement.md │ ├── feature-request.md │ └── question.md ├── PRIVACY.md ├── SECURITY.md ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── chrome.yml │ ├── dependency-review.yml │ ├── devskim.yml │ ├── edge.yml │ └── firefox.yml ├── .gitignore ├── LICENSE ├── README.md ├── src └── main │ ├── assets │ ├── icons │ │ ├── icon128.png │ │ ├── icon16.png │ │ ├── icon24.png │ │ ├── icon32.png │ │ └── icon48.png │ └── images │ │ ├── partner.png │ │ ├── switch_off.png │ │ └── switch_on.png │ ├── background.html │ ├── background.js │ ├── manifest.json │ ├── pages │ ├── popup │ │ ├── PopupPage.css │ │ ├── PopupPage.html │ │ └── PopupPage.js │ └── warning │ │ ├── WarningPage.css │ │ ├── WarningPage.html │ │ └── WarningPage.js │ ├── policies.json │ ├── protection │ ├── BrowserProtection.js │ └── ProtectionResult.js │ └── util │ ├── CacheManager.js │ ├── MessageType.js │ ├── Settings.js │ ├── Storage.js │ ├── UrlHelpers.js │ ├── hashing │ ├── MD5.js │ └── RC4.js │ └── other │ ├── EmsisoftUtil.js │ └── SmartScreenUtil.js └── tests └── protection-test-04-19-25.csv /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported 62 | to the community leaders responsible for enforcement in the project's **Issues** section. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | All community leaders are obligated to respect the privacy and security of the reporter of any incident. 65 | 66 | ## Enforcement Guidelines 67 | 68 | Community leaders will follow these Community Impact Guidelines in determining 69 | the consequences for any action they deem in violation of this Code of Conduct: 70 | 71 | ### 1. Correction 72 | 73 | **Community Impact**: Use of inappropriate language or other behavior deemed 74 | unprofessional or unwelcome in the community. 75 | 76 | **Consequence**: A private, written warning from community leaders, providing 77 | clarity around the nature of the violation and an explanation of why the 78 | behavior was inappropriate. A public apology may be requested. 79 | 80 | ### 2. Warning 81 | 82 | **Community Impact**: A violation through a single incident or series 83 | of actions. 84 | 85 | **Consequence**: A warning with consequences for continued behavior. No 86 | interaction with the people involved, including unsolicited interaction with 87 | those enforcing the Code of Conduct, for a specified period of time. This 88 | includes avoiding interactions in community spaces as well as external channels 89 | like social media. Violating these terms may lead to a temporary or 90 | permanent ban. 91 | 92 | ### 3. Temporary Ban 93 | 94 | **Community Impact**: A serious violation of community standards, including 95 | sustained inappropriate behavior. 96 | 97 | **Consequence**: A temporary ban from any sort of interaction or public 98 | communication with the community for a specified period of time. No public or 99 | private interaction with the people involved, including unsolicited interaction 100 | with those enforcing the Code of Conduct, is allowed during this period. 101 | Violating these terms may lead to a permanent ban. 102 | 103 | ### 4. Permanent Ban 104 | 105 | **Community Impact**: Demonstrating a pattern of violation of community 106 | standards, including sustained inappropriate behavior, harassment of an 107 | individual, or aggression toward or disparagement of classes of individuals. 108 | 109 | **Consequence**: A permanent ban from any sort of public interaction within 110 | the community. 111 | 112 | ## Attribution 113 | 114 | This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), 115 | version 2.0, available at https://contributor-covenant.org/version/2/0/code_of_conduct.html. 116 | 117 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 118 | enforcement ladder](https://github.com/mozilla/diversity). 119 | 120 | For answers to common questions about this code of conduct, see the FAQ at 121 | https://contributor-covenant.org/faq. Translations are available at 122 | https://contributor-covenant.org/translations. 123 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | Thank you for considering contributing to this project. Your help is appreciated. 4 | 5 | ## Code of Conduct 6 | 7 | This project and everyone participating in it is governed by the project's [Code of Conduct](CODE_OF_CONDUCT.md). 8 | 9 | By participating, you are expected to uphold this code. 10 | 11 | ## How Can I Contribute? 12 | 13 | ### Reporting Bugs 14 | 15 | This section guides you through submitting a bug report for this project. Following these guidelines helps maintainers 16 | and the community understand your report, reproduce the behavior, and find related reports. 17 | 18 | - Use a clear and descriptive title for the issue to identify the problem. 19 | - Describe the exact steps which reproduce the problem in as many details as possible. 20 | - Provide specific examples to demonstrate the steps. 21 | 22 | ### Suggesting Features 23 | 24 | This section guides you through submitting a feature request for this project, 25 | including completely new features and minor improvements to existing functionality. 26 | 27 | - Use a clear and descriptive title for the issue to identify the suggestion. 28 | - Provide a step-by-step description of the suggested improvement in as many details as possible. 29 | - Provide specific examples to demonstrate the steps. 30 | 31 | ### Pull Requests 32 | 33 | - Fill in the required template. 34 | - Include screenshots in your pull request whenever possible. 35 | - Make sure your code follows the project's coding standards. 36 | - Make sure all tests pass. 37 | 38 | Thank you for reading, and we look forward to your contributions! 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a bug 3 | about: Create a report to help us improve. 4 | title: '' 5 | labels: 'bug' 6 | assignees: 'Foulest' 7 | 8 | --- 9 | 10 | **Before you submit this, please make sure you have done the following:** 11 | 12 | - [ ] I am using the latest version of this project 13 | - [ ] I have checked the existing documentation for this project 14 | - [ ] I have searched the issue tracker for a similar issue 15 | 16 | **Describe the bug** 17 | A clear and concise description of what the bug is. 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Actual behavior** 23 | A clear and concise description of what actually happened. 24 | 25 | **To Reproduce** 26 | Steps to reproduce the behavior: 27 | 28 | 1. Go to '...' 29 | 2. Click on '....' 30 | 3. Scroll down to '....' 31 | 4. See error 32 | 33 | **Screenshots** 34 | If applicable, add screenshots or animated GIFs to help explain your problem. 35 | 36 | **Additional context** 37 | Add any other context about the issue here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation-improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation improvement 3 | about: Suggest an improvement for the project documentation. 4 | title: '' 5 | labels: 'documentation' 6 | assignees: 'Foulest' 7 | 8 | --- 9 | 10 | **Before you submit this, please make sure you have done the following:** 11 | 12 | - [ ] I am using the latest version of this project 13 | - [ ] I have checked the existing documentation for this project 14 | - [ ] I have searched the issue tracker for a similar issue 15 | 16 | **Describe the issue with the documentation** 17 | A clear and concise description of what the issue is. 18 | 19 | **Suggest how to fix or improve the documentation** 20 | Steps or details on how to improve the documentation. 21 | 22 | **Additional context** 23 | Add any other context about the issue here. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | title: '' 5 | labels: 'request' 6 | assignees: 'Foulest' 7 | 8 | --- 9 | 10 | **Before you submit this, please make sure you have done the following:** 11 | 12 | - [ ] I am using the latest version of this project 13 | - [ ] I have checked the existing documentation for this project 14 | - [ ] I have searched the issue tracker for a similar issue 15 | 16 | **Is your feature request related to a problem? Please describe.** 17 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 18 | 19 | **Describe the solution you'd like** 20 | A clear and concise description of what you want to happen. 21 | 22 | **Describe alternatives you've considered** 23 | A clear and concise description of any alternative solutions or features you've considered. 24 | 25 | **Additional context** 26 | Add any other context about the issue here. 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: Ask a question about the project. 4 | title: '' 5 | labels: 'question' 6 | assignees: 'Foulest' 7 | 8 | --- 9 | 10 | **Before you submit this, please make sure you have done the following:** 11 | 12 | - [ ] I am using the latest version of this project 13 | - [ ] I have checked the existing documentation for this project 14 | - [ ] I have searched the issue tracker for a similar issue 15 | 16 | **Your Question** 17 | Write your question here. Please provide as much detail as possible. 18 | 19 | **Additional context** 20 | Add any other context about the issue here. 21 | -------------------------------------------------------------------------------- /.github/PRIVACY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | **Effective Date:** 05/28/2025 4 | 5 | Osprey is committed to protecting your privacy. This Privacy Policy explains how our browser extension operates and how we handle your data. 6 | 7 | ## Data Collection and Use 8 | 9 | Osprey does not collect or store any personal data from its users. 10 | We do not have access to your browsing history or any other data that can personally identify you. 11 | 12 | ## URL Checking 13 | 14 | When you navigate to a website, Osprey may send the URL to the following third-party technologies to check for malicious content: 15 | 16 | - [PrecisionSec Web Protection](https://precisionsec.com/?utm_source=osprey) (Official Partner) 17 | - [Microsoft SmartScreen](https://learn.microsoft.com/en-us/windows/security/operating-system-security/virus-and-threat-protection/microsoft-defender-smartscreen) 18 | - [Symantec Browser Protection](https://chromewebstore.google.com/detail/symantec-browser-protecti/hielpjjagjimpgppnopiibaefhfpbpfn) 19 | - [Emsisoft Web Protection](https://emsisoft.com/en/help/1636/web-protection) 20 | - [Bitdefender TrafficLight](https://bitdefender.com/en-us/consumer/trafficlight) 21 | - [Norton SafeWeb](https://safeweb.norton.com) 22 | - [G DATA WebProtection](https://gdata.de/help/en/consumer/FAQ/webProtectionWinFAQ) 23 | - [Cloudflare Security DNS](https://blog.cloudflare.com/introducing-1-1-1-1-for-families/#two-flavors-1-1-1-2-no-malware-1-1-1-3-no-malware-or-adult-content) 24 | - [Quad9 Security DNS](https://quad9.net) 25 | - [DNS0.eu Security DNS](https://dns0.eu/zero) 26 | - [CleanBrowsing Security DNS](https://cleanbrowsing.org/filters/#step3) 27 | - [CIRA Canadian Shield DNS](https://cira.ca/en/canadian-shield) 28 | - [AdGuard Security DNS](https://adguard-dns.io/en/public-dns.html) 29 | - [Switch.ch Security DNS](https://switch.ch/en/dns-firewall) 30 | - [CERT-EE Security DNS](https://ria.ee/en/news/application-developed-cert-ee-protects-against-phishing-and-malware) 31 | - [Control D Security DNS](https://controld.com) 32 | 33 | These services are designed to enhance your browsing security by identifying potential threats. 34 | Please be aware that the data sent to these services is limited to the URL you are visiting and is not stored by Osprey. 35 | 36 | ## Third-Party Services 37 | 38 | The third-party services listed above may have their own privacy policies regarding the information they collect and how 39 | they use it. We encourage you to review their privacy policies for more information. 40 | 41 | ## Changes to This Privacy Policy 42 | 43 | We reserve the right to update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy 44 | on this page with a new effective date. We encourage you to review this Privacy Policy periodically for any updates. 45 | 46 | ## Affiliate Disclosure 47 | 48 | Osprey may contain affiliate links to products or services. If you click on an affiliate link and make a purchase, we 49 | may receive a commission at no additional cost to you. This helps support the development of Osprey. 50 | 51 | ## Getting Help 52 | 53 | For support or queries, please open an issue in the [Issues section](https://github.com/Foulest/Osprey/issues). 54 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | > **Note:** Only the most recent version of this project is supported with security updates. 4 | > 5 | > If you are using an older version, please update to the latest version. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you discover a vulnerability, please report it using the project's **Security** section. 10 | 11 | When reporting a vulnerability, please provide as much information as possible to help us understand the nature and 12 | scope of the potential issue. This should include: 13 | 14 | - A clear and detailed description of the issue 15 | - Steps to reproduce the issue, if applicable 16 | - The version of the project you are using 17 | - Any other relevant information 18 | 19 | We take all disclosures seriously and will do our best to address any issues in a timely manner. 20 | Please do not disclose the vulnerability publicly until we've had a chance to address it. 21 | 22 | Thank you for helping to keep this project and its users safe! 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | version: 2 6 | 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" # Check for updates daily 12 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Checklist** 2 | 3 | - [ ] I have read the [Contribution Guidelines](CONTRIBUTING.md). 4 | - [ ] I have searched the issue tracker for a similar issue. 5 | - [ ] I have added necessary documentation (if appropriate). 6 | - [ ] I have performed a self-review of my code. 7 | - [ ] My changes generate no new warnings or errors. 8 | 9 | **Related Issue(s)** 10 | Please link to the issue(s) this pull request is related to, if any. 11 | 12 | **Change(s) Made** 13 | A clear and concise description of what you changed. 14 | 15 | **Additional Context** 16 | Add any other context about the pull request here. 17 | -------------------------------------------------------------------------------- /.github/workflows/chrome.yml: -------------------------------------------------------------------------------- 1 | name: 'Compile for Chrome' 2 | 3 | on: 4 | push: 5 | branches: [ 'main' ] 6 | pull_request: 7 | branches: [ 'main' ] 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v4 19 | 20 | - name: 'Extract Version' 21 | id: get_version 22 | run: | 23 | VERSION=$(jq -r '.version' src/main/manifest.json) 24 | echo "VERSION=$VERSION" >> $GITHUB_ENV 25 | 26 | - name: 'Upload Artifact' 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: Osprey-${{ env.VERSION }}-chrome 30 | path: src/main 31 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 10 | name: 'Dependency Review' 11 | 12 | on: [ pull_request ] 13 | 14 | permissions: 15 | contents: read 16 | 17 | jobs: 18 | dependency-review: 19 | name: 'Dependency Review' 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: 'Checkout Repository' 24 | uses: actions/checkout@v4 25 | 26 | - name: 'Dependency Review' 27 | uses: actions/dependency-review-action@v4 28 | -------------------------------------------------------------------------------- /.github/workflows/devskim.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by separate 3 | # terms of service, privacy policy, and support documentation. 4 | name: 'DevSkim' 5 | 6 | on: 7 | push: 8 | branches: [ 'main' ] 9 | pull_request: 10 | branches: [ 'main' ] 11 | 12 | jobs: 13 | devskim: 14 | permissions: 15 | actions: read 16 | contents: read 17 | security-events: write 18 | statuses: write 19 | 20 | name: 'DevSkim' 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: 'Checkout Repository' 25 | uses: actions/checkout@v4 26 | 27 | - name: 'Run DevSkim Analysis' 28 | uses: microsoft/DevSkim-Action@v1 29 | 30 | - name: 'Upload DevSkim Results' 31 | uses: github/codeql-action/upload-sarif@v3 32 | with: 33 | sarif_file: devskim-results.sarif 34 | -------------------------------------------------------------------------------- /.github/workflows/edge.yml: -------------------------------------------------------------------------------- 1 | name: 'Compile for Edge' 2 | 3 | on: 4 | push: 5 | branches: [ 'main' ] 6 | pull_request: 7 | branches: [ 'main' ] 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v4 19 | 20 | - name: 'Extract Version' 21 | id: get_version 22 | run: | 23 | VERSION=$(jq -r '.version' src/main/manifest.json) 24 | echo "VERSION=$VERSION" >> $GITHUB_ENV 25 | 26 | - name: 'Remove update_url from manifest.json' 27 | run: | 28 | jq 'del(.update_url)' src/main/manifest.json > src/main/manifest_temp.json 29 | mv src/main/manifest_temp.json src/main/manifest.json 30 | 31 | - name: 'Upload Artifact' 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: Osprey-${{ env.VERSION }}-edge 35 | path: src/main 36 | -------------------------------------------------------------------------------- /.github/workflows/firefox.yml: -------------------------------------------------------------------------------- 1 | name: 'Compile for Firefox' 2 | 3 | on: 4 | push: 5 | branches: [ 'main' ] 6 | pull_request: 7 | branches: [ 'main' ] 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v4 19 | 20 | - name: 'Extract Version' 21 | id: get_version 22 | run: | 23 | VERSION=$(jq -r '.version' src/main/manifest.json) 24 | echo "VERSION=$VERSION" >> $GITHUB_ENV 25 | 26 | - name: 'Remove update_url from manifest.json' 27 | run: | 28 | jq 'del(.update_url)' src/main/manifest.json > src/main/manifest_temp.json 29 | mv src/main/manifest_temp.json src/main/manifest.json 30 | 31 | - name: 'Remove incognito from manifest.json' 32 | run: | 33 | jq 'del(.incognito)' src/main/manifest.json > src/main/manifest_temp.json 34 | mv src/main/manifest_temp.json src/main/manifest.json 35 | 36 | - name: 'Remove storage from manifest.json' 37 | run: | 38 | jq 'del(.storage)' src/main/manifest.json > src/main/manifest_temp.json 39 | mv src/main/manifest_temp.json src/main/manifest.json 40 | 41 | - name: 'Add menus permission' 42 | run: | 43 | jq '.permissions += ["menus"]' src/main/manifest.json > src/main/manifest_temp.json 44 | mv src/main/manifest_temp.json src/main/manifest.json 45 | 46 | - name: 'Replace service_worker with page' 47 | run: | 48 | jq '.background = {"page": "background.html"}' src/main/manifest.json > src/main/manifest_temp.json 49 | mv src/main/manifest_temp.json src/main/manifest.json 50 | 51 | - name: 'Add browser_specific_settings for Firefox' 52 | run: | 53 | jq '. += {"browser_specific_settings": {"gecko": {"id": "osprey@foulest.net", "strict_min_version": "109.0"}}}' src/main/manifest.json > src/main/manifest_temp.json 54 | mv src/main/manifest_temp.json src/main/manifest.json 55 | 56 | - name: 'Upload Artifact' 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: Osprey-${{ env.VERSION }}-firefox 60 | path: src/main 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows system files 2 | $RECYCLE.BIN/ 3 | Thumbs.db 4 | Thumbs.db:encryptable 5 | [Dd]esktop.ini 6 | ehthumbs.db 7 | ehthumbs_vista.db 8 | hs_err_pid* 9 | 10 | # macOS system files 11 | .AppleDB 12 | .AppleDesktop 13 | .AppleDouble 14 | .DS_Store 15 | .DocumentRevisions-V100 16 | .Icon 17 | .LSOverride 18 | .Network Trash Folder 19 | .Spotlight-V100 20 | .Temporary Items 21 | .TemporaryItems 22 | .Trash-* 23 | .Trashes 24 | .VolumeIcon.icns 25 | ._* 26 | .apdisk 27 | .com.apple.timemachine.donotpresent 28 | .fseventsd 29 | .fuse_hidden* 30 | 31 | # IDE and editor folders 32 | .buildpath/ 33 | .classpath/ 34 | .gradle/ 35 | .idea/ 36 | .project/ 37 | .settings/ 38 | *.iml 39 | *.ipr 40 | *.iws 41 | *.sublime-project 42 | *.sublime-workspace 43 | 44 | # Java compiled files 45 | *.class 46 | *.ear 47 | *.nar 48 | *.war 49 | 50 | # Maven files 51 | .mvn/ 52 | build/ 53 | out/ 54 | run/ 55 | target/ 56 | .flattened-pom.xml 57 | buildNumber.properties 58 | dependency-reduced-pom.xml 59 | pom.xml.next 60 | pom.xml.releaseBackup 61 | pom.xml.tag 62 | pom.xml.versionsBackup 63 | release.properties 64 | 65 | # Package files 66 | *.cab 67 | *.msi 68 | *.msix 69 | *.msm 70 | *.msp 71 | 72 | # Archive files 73 | *.7z 74 | *.rar 75 | *.tar.gz 76 | *.zip 77 | 78 | # Log and temporary files 79 | *.ctxt 80 | *.lnk 81 | *.log 82 | *.stackdump 83 | *~ 84 | .nfs* 85 | .tm_* 86 | 87 | # Misc 88 | machinet.conf 89 | 90 | # Node.js 91 | /node_modules/ 92 | /package-lock.json 93 | 94 | # Yarn 95 | /yarn.lock 96 | /.run/ 97 | 98 | # Extensions 99 | *.crx 100 | *.pem 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Osprey: Browser Protection 2 | 3 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE) 4 | [![CodeQL](https://github.com/Foulest/Osprey/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Foulest/Osprey/actions/workflows/github-code-scanning/codeql) 5 | ![Chrome Users](https://img.shields.io/chrome-web-store/users/jmnpibhfpmpfjhhkmpadlbgjnbhpjgnd?label=Chrome%20Users&color=00CC00) 6 | ![Edge Users](https://img.shields.io/badge/dynamic/json?label=Edge%20Users&color=00CC00&query=%24.activeInstallCount&url=https%3A%2F%2Fmicrosoftedge.microsoft.com%2Faddons%2Fgetproductdetailsbycrxid%2Fnopglhplnghfhpniofkcopmhbjdonlgn) 7 | ![Firefox Users](https://img.shields.io/amo/users/osprey-browser-protection?label=Firefox%20Users&color=00CC00) 8 | 9 | **Osprey** is a browser extension that protects you from malicious websites. 10 | 11 | [Google Chrome](https://chromewebstore.google.com/detail/osprey-browser-protection/jmnpibhfpmpfjhhkmpadlbgjnbhpjgnd) 12 | • [Microsoft Edge](https://microsoftedge.microsoft.com/addons/detail/osprey-browser-protectio/nopglhplnghfhpniofkcopmhbjdonlgn) 13 | • [Firefox](https://addons.mozilla.org/en-US/firefox/addon/osprey-browser-protection) 14 | • [Privacy Policy](https://github.com/Foulest/Osprey/blob/main/.github/PRIVACY.md) 15 | • [Wiki (FAQs)](https://github.com/Foulest/Osprey/wiki) 16 | 17 | ### 18 | ![Osprey Banner](https://i.imgur.com/0Ccn9WW.png) 19 | 20 | ## Official Partners 21 | 22 | Osprey has **officially partnered** with industry-leading security companies to provide you with the best protection 23 | possible. Check out some of our partners below: 24 | 25 | [![PrecisionSec](https://i.imgur.com/7jfyHtR.png)](https://precisionsec.com/?utm_source=osprey) 26 | 27 | ## Detections 28 | 29 | Osprey blocks websites that are classified as: 30 | 31 | - [Malicious](https://us.norton.com/blog/malware/what-are-malicious-websites) 32 | - [Phishing](https://f-secure.com/us-en/articles/what-is-phishing) 33 | - [Fraud](https://usa.kaspersky.com/resource-center/preemptive-safety/scam-websites) 34 | - [PUAs](https://us.norton.com/blog/malware/what-are-puas-potentially-unwanted-applications) 35 | - [Cryptojacking](https://kaspersky.com/resource-center/definitions/what-is-cryptojacking) 36 | - [Malvertising](https://malwarebytes.com/malvertising) 37 | - [Spam](https://developers.google.com/search/docs/essentials/spam-policies) 38 | - [Compromised](https://malwarebytes.com/glossary/compromised) 39 | - [Untrusted](https://mcafee.com/blogs/internet-security/how-to-tell-whether-a-website-is-safe-or-unsafe) 40 | 41 | ## Warning 42 | 43 | If the website is malicious, Osprey will block the page and display a warning: 44 | 45 | ![Osprey Warning](https://i.imgur.com/eu7BFrH.png) 46 | 47 | From this page, you can report the website as safe, temporarily allow the website, go back to safety, and continue 48 | anyway. By default, Osprey creates a browser notification for blocked pages that 49 | [you can toggle on and off](https://github.com/Foulest/Osprey/wiki/Toggling-Notifications) using the context menu. 50 | You can hide the continue and report buttons, lock down the protection options, and even hide the context menu 51 | entirely using [the system policies](https://github.com/Foulest/Osprey/wiki/Setting-Up-System-Policies). 52 | 53 | ## Settings 54 | 55 | You can configure the extension's protection options in the settings: 56 | 57 | ![Osprey Settings (Page 1)](https://i.imgur.com/urwoIO5.png) 58 | ![Osprey Settings (Page 2)](https://i.imgur.com/JOwyz81.png) 59 | 60 | ## Protection Providers 61 | 62 | The following providers are **enabled** by default: 63 | 64 | - [PrecisionSec Web Protection](https://precisionsec.com/?utm_source=osprey) (Official Partner) 65 | - [Microsoft SmartScreen](https://learn.microsoft.com/en-us/windows/security/operating-system-security/virus-and-threat-protection/microsoft-defender-smartscreen) 66 | - [Emsisoft Web Protection](https://emsisoft.com/en/help/1636/web-protection) 67 | - [Bitdefender TrafficLight](https://bitdefender.com/en-us/consumer/trafficlight) 68 | - [Norton SafeWeb](https://safeweb.norton.com) 69 | - [G DATA WebProtection](https://gdata.de/help/en/consumer/FAQ/webProtectionWinFAQ) 70 | - [Quad9 Security DNS](https://quad9.net) 71 | - [AdGuard Security DNS](https://adguard-dns.io/en/public-dns.html) 72 | - [Switch.ch Security DNS](https://switch.ch/en/dns-firewall) 73 | - [CERT-EE Security DNS](https://ria.ee/en/news/application-developed-cert-ee-protects-against-phishing-and-malware) 74 | - [Control D Security DNS](https://controld.com) 75 | 76 | The following providers are **disabled** by default: 77 | 78 | - [Symantec Browser Protection](https://chromewebstore.google.com/detail/symantec-browser-protecti/hielpjjagjimpgppnopiibaefhfpbpfn) 79 | - [Cloudflare Security DNS](https://blog.cloudflare.com/introducing-1-1-1-1-for-families/#two-flavors-1-1-1-2-no-malware-1-1-1-3-no-malware-or-adult-content) 80 | - [DNS0.eu Security DNS](https://dns0.eu/zero) 81 | - [CleanBrowsing Security DNS](https://cleanbrowsing.org/filters/#step3) 82 | - [CIRA Canadian Shield DNS](https://cira.ca/en/canadian-shield) 83 | 84 | Providers disabled by default are either due to frequent reports of false positives or an unreliable support email or 85 | website for reporting said false positives. If a provider gives you false positives, report the links to them directly 86 | and disable them in the Protection Options panel if needed. Osprey is designed to be customizable, so you can enable or 87 | disable any of the providers at any time. Osprey does not have control over the providers' databases or how they 88 | classify URLs. 89 | 90 | ## Privacy 91 | 92 | Osprey strips down each URL of tracking parameters before sending it to any APIs. 93 | 94 | For example: 95 | 96 | 1. If you search for shirts on Amazon and 97 | visit: https://www.amazon.com/s?k=shirts&crid=3TOVSW14ZHF8V&sprefix=shirt%2Caps%2C175&ref=nb_sb_noss_1 98 | 2. Osprey will only send https://amazon.com/s to any APIs you have enabled. 99 | 3. If the APIs report that the page is safe to visit, Osprey caches the result for 24 hours. 100 | 4. It will also be cached if you click 'Continue anyway' or 'Temporarily allow this website' on a blocked site. 101 | 5. As long as a URL is cached, no new network requests will be made for it. 102 | 103 | The only data the APIs receive is the stripped-down URL, your user agent, and your IP address. Use a reputable VPN or 104 | proxy service if you're concerned about IP-related privacy. There are also extensions that mask your user agent, if 105 | you're so inclined. 106 | 107 | As for why Osprey needs to check complete URLs instead of just the domain, many phishing attacks use legitimate 108 | companies to host their phishing campaigns, such as Jotform. If Osprey only checked a website's domain name, it wouldn't 109 | detect those threats. Osprey only sends your hostname to its various DNS API providers, so if you're highly concerned 110 | about URL page privacy, the DNS APIs are there for you. 111 | 112 | ## Installation 113 | 114 | You can install Osprey from the web stores listed at the top. 115 | 116 | For other installations, you can install the extension manually: 117 | 118 | ### Chrome/Edge 119 | 120 | 1. Navigate to the [Actions section](https://github.com/Foulest/Osprey/actions/workflows) and click `Compile for Chrome` 121 | or `Compile for Edge`. 122 | 2. Scroll down to the `Artifacts` section and download the artifact file. 123 | 3. Extract the artifact's ZIP file to a folder on your computer. 124 | 4. Navigate to `about://extensions` in your browser. 125 | 5. Enable `Developer mode` and click `Load unpacked`. 126 | 6. Select the downloaded ZIP file and click `Select Folder`. 127 | 128 | ### Firefox 129 | 130 | > Note: This only works 131 | > for [builds of Firefox that allow unsigned addons.](https://support.mozilla.org/en-US/kb/add-on-signing-in-firefox#w_what-are-my-options-if-i-want-to-use-an-unsigned-add-on-advanced-users) 132 | 133 | 1. Navigate to the [Actions section](https://github.com/Foulest/Osprey/actions/workflows) and click 134 | `Compile for Firefox`. 135 | 2. Scroll down to the `Artifacts` section and download the artifact file. 136 | 3. Extract the artifact's ZIP file to a folder on your computer. 137 | 4. Navigate to `about:addons` in your browser. 138 | 5. Click the gear icon and select `Install Add-on From File`. 139 | 6. Select the downloaded ZIP file and click `Select Folder`. 140 | 141 | Osprey should now be installed in your browser. 142 | 143 | ## Getting Help 144 | 145 | For support or queries, please open an issue in the [Issues section](https://github.com/Foulest/Osprey/issues). 146 | -------------------------------------------------------------------------------- /src/main/assets/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/icons/icon128.png -------------------------------------------------------------------------------- /src/main/assets/icons/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/icons/icon16.png -------------------------------------------------------------------------------- /src/main/assets/icons/icon24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/icons/icon24.png -------------------------------------------------------------------------------- /src/main/assets/icons/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/icons/icon32.png -------------------------------------------------------------------------------- /src/main/assets/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/icons/icon48.png -------------------------------------------------------------------------------- /src/main/assets/images/partner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/images/partner.png -------------------------------------------------------------------------------- /src/main/assets/images/switch_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/images/switch_off.png -------------------------------------------------------------------------------- /src/main/assets/images/switch_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foulest/Osprey/e2f8375cf4b9374f98a85b5e323c67f321ad00e2/src/main/assets/images/switch_on.png -------------------------------------------------------------------------------- /src/main/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Osprey", 3 | "name": "Osprey: Browser Protection", 4 | "manifest_version": 3, 5 | "version": "1.2.2", 6 | "description": "Protect against online threats in real-time using multiple browser protection engines.", 7 | "background": { 8 | "service_worker": "background.js" 9 | }, 10 | "action": { 11 | "default_icon": { 12 | "16": "assets/icons/icon16.png", 13 | "24": "assets/icons/icon24.png", 14 | "32": "assets/icons/icon32.png" 15 | }, 16 | "default_popup": "pages/popup/PopupPage.html" 17 | }, 18 | "content_security_policy": { 19 | "extension_pages": "script-src 'self'; object-src 'self'" 20 | }, 21 | "icons": { 22 | "16": "assets/icons/icon16.png", 23 | "48": "assets/icons/icon48.png", 24 | "128": "assets/icons/icon128.png" 25 | }, 26 | "permissions": [ 27 | "tabs", 28 | "storage", 29 | "webNavigation", 30 | "notifications", 31 | "contextMenus" 32 | ], 33 | "host_permissions": [ 34 | "*://*/*" 35 | ], 36 | "incognito": "split", 37 | "storage": { 38 | "managed_schema": "policies.json" 39 | }, 40 | "update_url": "https://clients2.google.com/service/update2/crx" 41 | } 42 | -------------------------------------------------------------------------------- /src/main/pages/popup/PopupPage.css: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------- 2 | General Styles 3 | -------------------------------------------------- */ 4 | html, body { 5 | margin: 0; 6 | font-family: 'Segoe UI', sans-serif; 7 | font-weight: 400; 8 | position: relative; 9 | box-sizing: border-box; 10 | } 11 | 12 | .gray { 13 | background-color: rgb(40, 40, 40); 14 | } 15 | 16 | .white { 17 | color: rgb(255, 255, 255) !important; 18 | } 19 | 20 | /* -------------------------------------------------- 21 | Banner 22 | -------------------------------------------------- */ 23 | .banner { 24 | display: flex; 25 | align-items: center; 26 | background-color: rgb(243, 186, 34); 27 | color: rgb(255, 255, 255); 28 | } 29 | 30 | .logo { 31 | width: 50px; 32 | margin-right: 15px; 33 | } 34 | 35 | .banner-text { 36 | font-size: 18px; 37 | text-shadow: 0 0 3px rgba(0, 0, 0, 0.5); 38 | } 39 | 40 | /* -------------------------------------------------- 41 | Alternate Section Text 42 | -------------------------------------------------- */ 43 | .alternate { 44 | color: rgb(255, 255, 255); 45 | } 46 | 47 | /* -------------------------------------------------- 48 | Headings 49 | -------------------------------------------------- */ 50 | h3 { 51 | font-size: 18px; 52 | color: rgb(177, 158, 37); 53 | padding: 0; 54 | margin-top: 10px; 55 | margin-bottom: 15px; 56 | } 57 | 58 | /* -------------------------------------------------- 59 | Section Styles 60 | -------------------------------------------------- */ 61 | section { 62 | padding: 5px 10px; 63 | font-size: 15px; 64 | line-height: 22px; 65 | font-weight: 400; 66 | margin: 0; 67 | } 68 | 69 | /* -------------------------------------------------- 70 | Status Text 71 | -------------------------------------------------- */ 72 | .status { 73 | vertical-align: middle; 74 | display: inline-block; 75 | font-size: 14px; 76 | margin: 5px 5px 15px 5px; 77 | } 78 | 79 | /* -------------------------------------------------- 80 | Switch Styles 81 | -------------------------------------------------- */ 82 | .switch { 83 | cursor: pointer; 84 | width: 32px; 85 | height: 17px; 86 | margin-top: 9px; 87 | display: inline-block; 88 | } 89 | 90 | .switch.on { 91 | background-image: url(../../assets/images/switch_on.png); 92 | } 93 | 94 | .switch.off { 95 | background-image: url(../../assets/images/switch_off.png); 96 | } 97 | 98 | /* -------------------------------------------------- 99 | Links 100 | -------------------------------------------------- */ 101 | .links a { 102 | font-size: 12px; 103 | color: rgb(177, 181, 184); 104 | display: inline-block; 105 | margin-top: 5px; 106 | margin-bottom: 5px; 107 | text-decoration: none; 108 | } 109 | 110 | #github:hover { 111 | text-decoration: underline; 112 | } 113 | 114 | /* -------------------------------------------------- 115 | Page Handling 116 | -------------------------------------------------- */ 117 | .title-container { 118 | display: flex; 119 | justify-content: space-between; 120 | align-items: center; 121 | } 122 | 123 | .pagination { 124 | display: flex; 125 | align-items: center; 126 | padding: 0; 127 | margin-top: 11px; 128 | margin-bottom: 15px; 129 | } 130 | 131 | .arrow { 132 | cursor: pointer; 133 | padding: 0 5px; 134 | font-size: 18px; 135 | } 136 | 137 | .page-indicator { 138 | font-size: 16px; 139 | } 140 | 141 | .page { 142 | display: none; 143 | height: 457px; 144 | width: 320px; 145 | overflow-x: hidden; 146 | overflow-y: auto; 147 | } 148 | 149 | .page.active { 150 | display: block; 151 | } 152 | 153 | /* -------------------------------------------------- 154 | Partner Labels 155 | -------------------------------------------------- */ 156 | .partner-label { 157 | position: relative; 158 | bottom: 2px; 159 | left: 2px; 160 | color: rgb(255, 255, 255); 161 | text-decoration: none; 162 | } 163 | 164 | .partner-label:hover { 165 | text-decoration: underline; 166 | } 167 | -------------------------------------------------------------------------------- /src/main/pages/popup/PopupPage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Osprey: Browser Protection 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 |
23 |
24 |

25 | Protection Options 26 |

27 | 32 |
33 | 34 |
35 | Official Partner 36 | PrecisionSec Web Protection 37 |
38 | 39 | Active 40 |
41 | 42 | Microsoft SmartScreen 43 |
44 | 45 | Active 46 |
47 | 48 | Symantec Browser Protection 49 |
50 | 51 | Active 52 |
53 | 54 | Emsisoft Web Protection 55 |
56 | 57 | Active 58 |
59 | 60 | Bitdefender TrafficLight 61 |
62 | 63 | Active 64 |
65 | 66 | Norton SafeWeb 67 |
68 | 69 | Active 70 |
71 | 72 | G DATA WebProtection 73 |
74 | 75 | Active 76 |
77 |
78 | 79 |
80 | Cloudflare Security DNS 81 |
82 | 83 | Active 84 |
85 | 86 | Quad9 Security DNS 87 |
88 | 89 | Active 90 |
91 | 92 | DNS0.eu Security DNS 93 |
94 | 95 | Active 96 |
97 | 98 | CleanBrowsing Security DNS 99 |
100 | 101 | Active 102 |
103 | 104 | CIRA Canadian Shield DNS 105 |
106 | 107 | Active 108 |
109 | 110 | AdGuard Security DNS 111 |
112 | 113 | Active 114 |
115 | 116 | Switch.ch Security DNS 117 |
118 | 119 | Active 120 |
121 |
122 | 123 |
124 | CERT-EE Security DNS 125 |
126 | 127 | Active 128 |
129 | 130 | Control D Security DNS 131 |
132 | 133 | Active 134 |
135 |
136 | 137 | 142 |
143 | 144 | 145 | -------------------------------------------------------------------------------- /src/main/pages/popup/PopupPage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Use a global singleton pattern to ensure we don't duplicate resources 4 | window.PopupSingleton = window.PopupSingleton || (function () { 5 | // Track initialization state 6 | let isInitialized = false; 7 | 8 | // Cache for DOM elements 9 | const domElements = {}; 10 | 11 | // Browser API compatibility between Chrome and Firefox 12 | const browserAPI = typeof browser === 'undefined' ? chrome : browser; 13 | 14 | // Security systems configuration - only defined once 15 | const securitySystems = [ 16 | { 17 | name: "precisionSecEnabled", 18 | title: "PrecisionSec Web Protection", 19 | labelElementId: "precisionSecStatus", 20 | switchElementId: "precisionSecSwitch", 21 | messageType: Messages.MessageType.PRECISIONSEC_TOGGLED, 22 | }, 23 | { 24 | name: "smartScreenEnabled", 25 | title: "Microsoft SmartScreen", 26 | labelElementId: "smartScreenStatus", 27 | switchElementId: "smartScreenSwitch", 28 | messageType: Messages.MessageType.SMARTSCREEN_TOGGLED, 29 | }, 30 | { 31 | name: "symantecEnabled", 32 | title: "Symantec Browser Protection", 33 | labelElementId: "symantecStatus", 34 | switchElementId: "symantecSwitch", 35 | messageType: Messages.MessageType.SYMANTEC_TOGGLED, 36 | }, 37 | { 38 | name: "emsisoftEnabled", 39 | title: "Emsisoft Web Protection", 40 | labelElementId: "emsisoftStatus", 41 | switchElementId: "emsisoftSwitch", 42 | messageType: Messages.MessageType.EMSISOFT_TOGGLED, 43 | }, 44 | { 45 | name: "bitdefenderEnabled", 46 | title: "Bitdefender TrafficLight", 47 | labelElementId: "bitdefenderStatus", 48 | switchElementId: "bitdefenderSwitch", 49 | messageType: Messages.MessageType.BITDEFENDER_TOGGLED, 50 | }, 51 | { 52 | name: "nortonEnabled", 53 | title: "Norton SafeWeb", 54 | labelElementId: "nortonStatus", 55 | switchElementId: "nortonSwitch", 56 | messageType: Messages.MessageType.NORTON_TOGGLED, 57 | }, 58 | { 59 | name: "gDataEnabled", 60 | title: "G DATA WebProtection", 61 | labelElementId: "gDataStatus", 62 | switchElementId: "gDataSwitch", 63 | messageType: Messages.MessageType.G_DATA_TOGGLED, 64 | }, 65 | { 66 | name: "cloudflareEnabled", 67 | title: "Cloudflare Security DNS", 68 | labelElementId: "cloudflareStatus", 69 | switchElementId: "cloudflareSwitch", 70 | messageType: Messages.MessageType.CLOUDFLARE_TOGGLED, 71 | }, 72 | { 73 | name: "quad9Enabled", 74 | title: "Quad9 Security DNS", 75 | labelElementId: "quad9Status", 76 | switchElementId: "quad9Switch", 77 | messageType: Messages.MessageType.QUAD9_TOGGLED, 78 | }, 79 | { 80 | name: "dns0Enabled", 81 | title: "DNS0.eu Security DNS", 82 | labelElementId: "dns0Status", 83 | switchElementId: "dns0Switch", 84 | messageType: Messages.MessageType.DNS0_TOGGLED, 85 | }, 86 | { 87 | name: "cleanBrowsingEnabled", 88 | title: "CleanBrowsing Security DNS", 89 | labelElementId: "cleanBrowsingStatus", 90 | switchElementId: "cleanBrowsingSwitch", 91 | messageType: Messages.MessageType.CLEAN_BROWSING_TOGGLED, 92 | }, 93 | { 94 | name: "ciraEnabled", 95 | title: "CIRA Canadian Shield DNS", 96 | labelElementId: "ciraStatus", 97 | switchElementId: "ciraSwitch", 98 | messageType: Messages.MessageType.CIRA_TOGGLED, 99 | }, 100 | { 101 | name: "adGuardEnabled", 102 | title: "AdGuard Security DNS", 103 | labelElementId: "adGuardStatus", 104 | switchElementId: "adGuardSwitch", 105 | messageType: Messages.MessageType.ADGUARD_TOGGLED, 106 | }, 107 | { 108 | name: "switchCHEnabled", 109 | title: "Switch.ch Security DNS", 110 | labelElementId: "switchCHStatus", 111 | switchElementId: "switchCHSwitch", 112 | messageType: Messages.MessageType.SWITCH_CH_TOGGLED, 113 | }, 114 | { 115 | name: "certEEEnabled", 116 | title: "CERT-EE Security DNS", 117 | labelElementId: "certEEStatus", 118 | switchElementId: "certEESwitch", 119 | messageType: Messages.MessageType.CERT_EE_TOGGLED, 120 | }, 121 | { 122 | name: "controlDEnabled", 123 | title: "Control D Security DNS", 124 | labelElementId: "controlDStatus", 125 | switchElementId: "controlDSwitch", 126 | messageType: Messages.MessageType.CONTROL_D_TOGGLED, 127 | } 128 | ]; 129 | 130 | /** 131 | * Get DOM elements for a system, caching them for future use 132 | * 133 | * @param {Object} system - The system object 134 | * @returns {Object} Object containing the label and switch elements 135 | */ 136 | const getSystemElements = function (system) { 137 | if (!domElements[system.name]) { 138 | domElements[system.name] = { 139 | label: document.getElementById(system.labelElementId), 140 | switchElement: document.getElementById(system.switchElementId) 141 | }; 142 | } 143 | return domElements[system.name]; 144 | }; 145 | 146 | /** 147 | * Batch updates UI elements for better performance 148 | * 149 | * @param {Array} updates - Array of update operations to perform 150 | */ 151 | const batchDomUpdates = function (updates) { 152 | window.requestAnimationFrame(() => { 153 | updates.forEach(update => update()); 154 | }); 155 | }; 156 | 157 | /** 158 | * Updates the UI for a specific security system using batched DOM operations. 159 | * 160 | * @param {Object} system - The system object being updated. 161 | * @param {boolean} isOn - Whether the protection is enabled for the system. 162 | */ 163 | const updateProtectionStatusUI = function (system, isOn) { 164 | const updates = []; 165 | 166 | // Get cached DOM elements or fetch them if not cached 167 | const elements = getSystemElements(system); 168 | 169 | updates.push(() => { 170 | if (elements.label) { 171 | Settings.get(settings => { 172 | if (settings.lockProtectionOptions) { 173 | elements.label.textContent = isOn ? "On (Locked)" : "Off (Locked)"; 174 | } else { 175 | elements.label.textContent = isOn ? "On" : "Off"; 176 | } 177 | }); 178 | } 179 | 180 | if (elements.switchElement) { 181 | if (isOn) { 182 | elements.switchElement.classList.add("on"); 183 | elements.switchElement.classList.remove("off"); 184 | } else { 185 | elements.switchElement.classList.remove("on"); 186 | elements.switchElement.classList.add("off"); 187 | } 188 | } 189 | }); 190 | 191 | batchDomUpdates(updates); 192 | }; 193 | 194 | /** 195 | * Toggles the state of a security system and updates its UI. 196 | * 197 | * @param {Object} system - The system object being toggled. 198 | */ 199 | const toggleProtection = function (system) { 200 | Settings.get(settings => { 201 | const currentState = settings[system.name]; 202 | const newState = !currentState; 203 | 204 | Settings.set({[system.name]: newState}, () => { 205 | updateProtectionStatusUI(system, newState); 206 | 207 | browserAPI.runtime.sendMessage({ 208 | messageType: system.messageType, 209 | title: system.title, 210 | toggleState: newState, 211 | }); 212 | }); 213 | }); 214 | }; 215 | 216 | /** 217 | * Reset to initial state to prevent memory leaks 218 | */ 219 | const reset = function () { 220 | // Remove click handlers from all switches 221 | securitySystems.forEach(system => { 222 | const elements = domElements[system.name]; 223 | 224 | if (elements && elements.switchElement) { 225 | elements.switchElement.onclick = null; 226 | } 227 | }); 228 | 229 | // Keep the DOM elements cache but reset initialization 230 | isInitialized = false; 231 | }; 232 | 233 | /** 234 | * Initialize the popup or refresh if already initialized 235 | */ 236 | const initialize = function () { 237 | // If already initialized, reset first 238 | if (isInitialized) { 239 | reset(); 240 | } 241 | 242 | // Mark as initialized 243 | isInitialized = true; 244 | 245 | // Set up switch elements and click handlers 246 | securitySystems.forEach(system => { 247 | const elements = getSystemElements(system); 248 | 249 | if (elements.switchElement) { 250 | elements.switchElement.onclick = () => { 251 | Settings.get(settings => { 252 | if (settings.lockProtectionOptions) { 253 | console.debug("Protections are locked; cannot toggle."); 254 | } else { 255 | toggleProtection(system); 256 | } 257 | }); 258 | } 259 | } 260 | }); 261 | 262 | // Load and apply settings 263 | Settings.get(settings => { 264 | securitySystems.forEach(system => { 265 | const isEnabled = settings[system.name]; 266 | updateProtectionStatusUI(system, isEnabled); 267 | }); 268 | }); 269 | 270 | // Update version display 271 | const versionElement = document.getElementById("version"); 272 | if (versionElement) { 273 | const manifest = browserAPI.runtime.getManifest(); 274 | const version = manifest.version; 275 | versionElement.textContent += version; 276 | } 277 | 278 | const page1 = document.getElementById("page1"); 279 | const page2 = document.getElementById("page2"); 280 | const page3 = document.getElementById("page3"); 281 | const prevPage = document.getElementById("prevPage"); 282 | const nextPage = document.getElementById("nextPage"); 283 | const pageIndicator = document.getElementById("pageIndicator"); 284 | let currentPage = 1; 285 | const totalPages = 3; 286 | 287 | function updatePageDisplay() { 288 | // Hide all pages 289 | page1.classList.remove("active"); 290 | page2.classList.remove("active"); 291 | page3.classList.remove("active"); 292 | 293 | // Show current page 294 | if (currentPage === 1) { 295 | page1.classList.add("active"); 296 | } else if (currentPage === 2) { 297 | page2.classList.add("active"); 298 | } else if (currentPage === 3) { 299 | page3.classList.add("active"); 300 | } 301 | 302 | // Update page indicator 303 | pageIndicator.textContent = `${currentPage}/${totalPages}`; 304 | } 305 | 306 | prevPage.addEventListener("click", function () { 307 | currentPage = currentPage === 1 ? totalPages : currentPage - 1; 308 | updatePageDisplay(); 309 | }); 310 | 311 | nextPage.addEventListener("click", function () { 312 | currentPage = currentPage === totalPages ? 1 : currentPage + 1; 313 | updatePageDisplay(); 314 | }); 315 | 316 | // Initialize display 317 | updatePageDisplay(); 318 | }; 319 | 320 | // Public API 321 | return { 322 | initialize, 323 | reset 324 | }; 325 | })(); 326 | 327 | // Initialize when DOM is ready 328 | document.addEventListener("DOMContentLoaded", () => { 329 | window.PopupSingleton.initialize(); 330 | }); 331 | -------------------------------------------------------------------------------- /src/main/pages/warning/WarningPage.css: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------- 2 | General Styles 3 | -------------------------------------------------- */ 4 | html, 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | font-family: 'Segoe UI', sans-serif; 9 | } 10 | 11 | .gray { 12 | background-color: rgb(40, 40, 40); 13 | } 14 | 15 | .white { 16 | color: rgb(255, 255, 255) !important; 17 | } 18 | 19 | /* -------------------------------------------------- 20 | Layout 21 | ----------------------------------------------------- */ 22 | #Wrapper { 23 | display: flex; 24 | justify-content: center; 25 | align-items: center; 26 | padding: 48px 0 0; 27 | } 28 | 29 | /* -------------------------------------------------- 30 | Banner 31 | -------------------------------------------------- */ 32 | .banner { 33 | display: flex; 34 | align-items: center; 35 | background-color: rgb(243, 186, 34); 36 | color: rgb(255, 255, 255); 37 | } 38 | 39 | .logo { 40 | width: 50px; 41 | height: auto; 42 | margin: 0 15px 0 0; 43 | padding: 0; 44 | } 45 | 46 | .banner-text { 47 | font-size: 18px; 48 | text-shadow: 0 0 3px rgba(0, 0, 0, 0.5); 49 | } 50 | 51 | /* -------------------------------------------------- 52 | Typography 53 | -------------------------------------------------- */ 54 | h2 { 55 | margin: 0 0 0 -2px; 56 | padding: 4px 0; 57 | line-height: 32px; 58 | font-size: 28px; 59 | font-family: 'Segoe UI Light', 'Segoe UI', sans-serif; 60 | font-weight: 400; 61 | color: rgb(31, 31, 31); 62 | } 63 | 64 | p { 65 | line-height: 22px; 66 | font-size: 15px; 67 | font-weight: 400; 68 | color: rgb(255, 255, 255); 69 | margin: 16px 0 0 0; 70 | padding: 0; 71 | } 72 | 73 | /* -------------------------------------------------- 74 | Content Area 75 | -------------------------------------------------- */ 76 | .msgBody { 77 | max-width: 500px; 78 | display: inline-block; 79 | vertical-align: top; 80 | margin: 0 0 0 20px; 81 | } 82 | 83 | /* -------------------------------------------------- 84 | Buttons 85 | -------------------------------------------------- */ 86 | .white-pushbutton { 87 | display: inline-block; 88 | font-size: 15px; 89 | color: rgb(255, 255, 255); 90 | height: 36px; 91 | padding: 0 16px; 92 | text-align: center; 93 | margin-bottom: 8px; 94 | margin-right: 4px; 95 | border: 2px solid transparent; 96 | white-space: nowrap; 97 | background-color: rgb(243, 183, 34); 98 | text-shadow: 0 0 3px rgba(0, 0, 0, 0.5); 99 | transition: height 0s; 100 | } 101 | 102 | .white-pushbutton:active { 103 | color: rgb(255, 255, 255); 104 | background-color: rgb(191, 141, 26); 105 | transition-duration: 1ms; 106 | transition-timing-function: ease-out; 107 | } 108 | 109 | .white-pushbutton:hover { 110 | border: 2px solid rgb(191, 141, 26); 111 | transition-duration: 0.15s; 112 | transition-timing-function: ease-out; 113 | cursor: pointer; 114 | } 115 | 116 | #continueButton { 117 | background-color: transparent; 118 | border: 2px solid rgb(243, 186, 34); 119 | text-shadow: 0 0 3px rgba(0, 0, 0, 0.5); 120 | color: rgb(243, 186, 34); 121 | transition: background-color 0.15s ease-out, color 0.15s ease-out; 122 | } 123 | 124 | #continueButton:hover { 125 | background-color: rgba(243, 186, 34, 0.1); 126 | border-color: rgb(243, 186, 34); 127 | color: rgb(243, 186, 34); 128 | } 129 | 130 | #reportSite, 131 | #allowSite { 132 | color: rgb(243, 186, 34); 133 | text-decoration: underline; 134 | } 135 | 136 | #reportSite:hover, 137 | #allowSite:hover { 138 | cursor: pointer; 139 | } 140 | 141 | /* -------------------------------------------------- 142 | Responsive Styles 143 | -------------------------------------------------- */ 144 | @media screen and (max-width: 610px) { 145 | p { 146 | margin: 16px 6% 0 0; 147 | } 148 | 149 | .msgBody { 150 | width: 92%; 151 | max-width: none; 152 | margin: 0 0 0 6%; 153 | padding: 0; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/main/pages/warning/WarningPage.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Osprey: Browser Protection 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 23 |
24 |
25 |

26 | Warning: This website is unsafe 27 |

28 | 29 |

30 | 31 | Osprey has blocked access to this website. It is unsafe to continue. 32 | 33 |

34 | 35 |

36 | 37 | If you are the owner of this website, please contact the security provider to resolve the issue. 38 | 39 |

40 | 41 |

42 | URL:
43 | Reported by:
44 | Reason: 45 |

46 | 47 |

48 | 49 | 50 | 51 |

52 | 53 |
54 | 57 | 58 | 61 |
62 |
63 |
64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/pages/warning/WarningPage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Use a global singleton pattern to ensure we don't duplicate resources 4 | window.WarningSingleton = window.WarningSingleton || (function () { 5 | // Browser API compatibility between Chrome and Firefox 6 | const browserAPI = typeof browser === 'undefined' ? chrome : browser; 7 | 8 | /** 9 | * Initialize the popup or refresh if already initialized 10 | */ 11 | const initialize = function () { 12 | // Extract the threat code from the current page URL 13 | const pageUrl = window.document.URL; 14 | const result = UrlHelpers.extractResult(pageUrl); 15 | 16 | // Set the reason text based on the result 17 | if (!result) { 18 | console.warn("No result found in the URL."); 19 | return; 20 | } 21 | 22 | // Cache for DOM elements 23 | const domElements = Object.fromEntries( 24 | ["reason", "url", "reportedBy", "reportSite", "allowSite", "homepageButton", "continueButton"] 25 | .map(id => [id, document.getElementById(id)]) 26 | ); 27 | 28 | domElements.reason.innerText = result; 29 | 30 | // Extract the blocked URL from the current page URL 31 | const blockedUrl = UrlHelpers.extractBlockedUrl(pageUrl); 32 | 33 | // Encode the URLs for safe use in other contexts 34 | const encodedBlockedUrl = encodeURIComponent(blockedUrl); 35 | const encodedResult = encodeURIComponent(result); 36 | 37 | // Set the URL text to the current page URL 38 | domElements.url.innerText = blockedUrl; 39 | 40 | // Get origin information 41 | const origin = UrlHelpers.extractOrigin(pageUrl); 42 | const originInt = parseInt(origin); 43 | const systemName = ProtectionResult.ResultOriginNames[originInt]; 44 | 45 | // Set reported by text 46 | domElements.reportedBy.innerText = systemName || "Unknown"; 47 | 48 | // Create a function to get the report URL lazily when needed 49 | const getReportUrl = () => { 50 | switch (originInt) { 51 | case ProtectionResult.ResultOrigin.PRECISIONSEC: 52 | // Verified working as of: May 28, 2025 53 | // Response time: 1-2 days 54 | return new URL("mailto:info@precisionsec.com?subject=False%20Positive&body=Hello%2C" 55 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 56 | + "%0A%0AProduct%3A%20PrecisionSec%20Web%20Protection" 57 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 58 | + "%0ADetected%20as%3A%20" + encodedResult 59 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 60 | 61 | case ProtectionResult.ResultOrigin.SMARTSCREEN: 62 | return new URL("https://feedback.smartscreen.microsoft.com/feedback.aspx?t=16&url=" + blockedUrl); 63 | 64 | case ProtectionResult.ResultOrigin.SYMANTEC: 65 | return new URL("https://sitereview.symantec.com/sitereview.jsp?referrer=sedsbp&url=" + encodedBlockedUrl); 66 | 67 | case ProtectionResult.ResultOrigin.EMSISOFT: 68 | // Verified working as of: May 1, 2025 69 | // Response time: 1-2 days 70 | return new URL("mailto:fp@emsisoft.com?subject=False%20Positive&body=Hello%2C" 71 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 72 | + "%0A%0AProduct%3A%20Emsisoft%20Browser%20Security" 73 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 74 | + "%0ADetected%20as%3A%20" + encodedResult 75 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 76 | 77 | case ProtectionResult.ResultOrigin.BITDEFENDER: 78 | return new URL("https://bitdefender.com/consumer/support/answer/29358/#scroll-to-heading-2"); 79 | 80 | case ProtectionResult.ResultOrigin.NORTON: 81 | return new URL("https://safeweb.norton.com/report?url=" + encodedBlockedUrl); 82 | 83 | case ProtectionResult.ResultOrigin.G_DATA: 84 | // Old URL: "https://submit.gdatasoftware.com/privacy" 85 | // TODO: Needs verification of response from support team. 86 | return new URL("mailto:support-us@gdata-software.com?subject=False%20Positive&body=Hello%2C" 87 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 88 | + "%0A%0AProduct%3A%20G%20DATA%20WebProtection" 89 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 90 | + "%0ADetected%20as%3A%20" + encodedResult 91 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 92 | 93 | case ProtectionResult.ResultOrigin.CLOUDFLARE: 94 | return new URL("https://radar.cloudflare.com/domains/feedback/" + encodedBlockedUrl); 95 | 96 | case ProtectionResult.ResultOrigin.QUAD9: 97 | // Old URL: "https://quad9.net/support/contact" 98 | // TODO: Needs verification of response from support team. 99 | return new URL("mailto:support@quad9.net?subject=False%20Positive&body=Hello%2C" 100 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 101 | + "%0A%0AProduct%3A%20Quad9%20DNS" 102 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 103 | + "%0ADetected%20as%3A%20" + encodedResult 104 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 105 | 106 | case ProtectionResult.ResultOrigin.DNS0: 107 | return new URL("https://www.dns0.eu/report"); 108 | 109 | case ProtectionResult.ResultOrigin.CLEANBROWSING: 110 | // Verified working as of: May 12, 2025 111 | // Response time: 1-2 days 112 | return new URL("mailto:support@cleanbrowsing.org?subject=False%20Positive&body=Hello%2C" 113 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 114 | + "%0A%0AProduct%3A%20CleanBrowsing%20Security%20Filter" 115 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 116 | + "%0ADetected%20as%3A%20" + encodedResult 117 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 118 | 119 | case ProtectionResult.ResultOrigin.CIRA: 120 | // Alternate URL: "https://www.cira.ca/en/canadian-shield/support" 121 | // TODO: Needs verification of response from support team. 122 | // Report sent on: May 18, 2025 - no response yet; false positive still active 123 | return new URL("mailto:info@cira.ca?subject=False%20Positive&body=Hello%2C" 124 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 125 | + "%0A%0AProduct%3A%20CIRA%20Canadian%20Shield%20DNS" 126 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 127 | + "%0ADetected%20as%3A%20" + encodedResult 128 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 129 | 130 | case ProtectionResult.ResultOrigin.ADGUARD: 131 | // Verified working as of: May 25, 2025 132 | // Response time: 1-2 days 133 | return new URL("mailto:support@adguard.com?subject=False%20Positive&body=Hello%2C" 134 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 135 | + "%0A%0AProduct%3A%20AdGuard%20DNS" 136 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 137 | + "%0ADetected%20as%3A%20" + encodedResult 138 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 139 | 140 | case ProtectionResult.ResultOrigin.SWITCH_CH: 141 | // TODO: Needs verification of response from support team. 142 | return new URL("mailto:info@switch.ch?subject=False%20Positive&body=Hello%2C" 143 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 144 | + "%0A%0AProduct%3A%20Switch.ch%20DNS" 145 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 146 | + "%0ADetected%20as%3A%20" + encodedResult 147 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 148 | 149 | case ProtectionResult.ResultOrigin.CERT_EE: 150 | // Verified working as of: May 6, 2025 151 | // Response time: 2-3 days 152 | return new URL("mailto:ria@ria.ee?subject=False%20Positive&body=Hello%2C" 153 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 154 | + "%0A%0AProduct%3A%20CERT-EE%20DNS" 155 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 156 | + "%0ADetected%20as%3A%20" + encodedResult 157 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 158 | 159 | case ProtectionResult.ResultOrigin.CONTROL_D: 160 | // TODO: Needs verification of response from support team. 161 | return new URL("mailto:help@controld.com?subject=False%20Positive&body=Hello%2C" 162 | + "%0A%0AI%20would%20like%20to%20report%20a%20false%20positive." 163 | + "%0A%0AProduct%3A%20Control%20D%20DNS" 164 | + "%0AURL%3A%20" + encodedBlockedUrl + "%20%28or%20the%20hostname%20itself%29" 165 | + "%0ADetected%20as%3A%20" + encodedResult 166 | + "%0A%0AI%20believe%20this%20website%20is%20legitimate.%0A%0AThanks."); 167 | 168 | default: 169 | return null; 170 | } 171 | }; 172 | 173 | // Unified message sending function with error handling 174 | const sendMessage = async (messageType, additionalData = {}) => { 175 | try { 176 | // Convert URL objects to strings before sending 177 | const message = { 178 | messageType, 179 | blockedUrl: blockedUrl instanceof URL ? blockedUrl.toString() : blockedUrl, 180 | origin: origin instanceof URL ? origin.toString() : origin, 181 | ...additionalData 182 | }; 183 | 184 | // Also check any properties in additionalData that might be URL objects 185 | for (const key in message) { 186 | if (message[key] instanceof URL) { 187 | message[key] = message[key].toString(); 188 | } 189 | } 190 | 191 | await browserAPI.runtime.sendMessage(message); 192 | } catch (error) { 193 | console.error(`Error sending message ${messageType}:`, error); 194 | } 195 | }; 196 | 197 | // Add event listener to "Report this website as safe" button 198 | Settings.get(settings => { 199 | domElements.reportSite.addEventListener("click", async () => { 200 | if (!settings.hideReportButton) { 201 | await sendMessage(Messages.MessageType.REPORT_SITE, { 202 | reportUrl: getReportUrl() 203 | }); 204 | } 205 | }); 206 | 207 | // Add event listener to "Temporarily allow this website" button 208 | domElements.allowSite.addEventListener("click", async () => { 209 | if (!settings.hideContinueButtons) { 210 | await sendMessage(Messages.MessageType.ALLOW_SITE, { 211 | blockedUrl: blockedUrl 212 | }); 213 | } 214 | }); 215 | 216 | // Add event listener to "Back to safety" button 217 | domElements.homepageButton.addEventListener("click", async () => { 218 | await sendMessage(Messages.MessageType.CONTINUE_TO_SAFETY, { 219 | blockedUrl: blockedUrl 220 | }); 221 | }); 222 | 223 | // Add event listener to "Continue anyway" button 224 | domElements.continueButton.addEventListener("click", async () => { 225 | if (!settings.hideContinueButtons) { 226 | await sendMessage(Messages.MessageType.CONTINUE_TO_SITE, { 227 | blockedUrl: blockedUrl 228 | }); 229 | } 230 | }); 231 | 232 | // Checks for overrides to any HTML elements from settings 233 | if (!settings.hideContinueButtons) { 234 | document.getElementById("allowSite").style.display = ""; 235 | document.getElementById("continueButton").style.display = ""; 236 | } 237 | 238 | if (!settings.hideReportButton) { 239 | document.getElementById("reportSite").style.display = ""; 240 | document.getElementById("reportBreakpoint").style.display = ""; 241 | } 242 | }); 243 | }; 244 | 245 | // Public API 246 | return { 247 | initialize 248 | }; 249 | })(); 250 | 251 | document.addEventListener("DOMContentLoaded", () => { 252 | // Initialize the singleton instance 253 | window.WarningSingleton.initialize(); 254 | }); 255 | -------------------------------------------------------------------------------- /src/main/policies.json: -------------------------------------------------------------------------------- 1 | { 2 | "_comment_1": "https://chromium.org/administrators/configuring-policy-for-extensions", 3 | "_comment_2": "In Windows, these policies are stored under HKLM\\SOFTWARE\\Policies\\Google\\Chrome\\3rdParty\\Extensions\\\\policy", 4 | "_comment_3": "e.g. a REG_DWORD named 'DisableNotifications' with a value of 1 will result in the notifications being disabled.", 5 | "_comment_4": "The extension ID is found in the chrome://extensions or edge://extensions page.", 6 | "type": "object", 7 | "properties": { 8 | "DisableNotifications": { 9 | "title": "Disable Notifications", 10 | "description": "If set to true, notifications will be disabled.", 11 | "type": "boolean" 12 | }, 13 | "DisableContextMenu": { 14 | "title": "Disable Context Menu", 15 | "description": "If set to true, the context menu will be disabled.", 16 | "type": "boolean" 17 | }, 18 | "HideContinueButtons": { 19 | "title": "Hide Continue Buttons", 20 | "description": "If set to true, the continue buttons will be hidden.", 21 | "type": "boolean" 22 | }, 23 | "HideReportButton": { 24 | "title": "Hide Report Button", 25 | "description": "If set to true, the report button will be hidden.", 26 | "type": "boolean" 27 | }, 28 | "LockProtectionOptions": { 29 | "title": "Lock Protection Options", 30 | "description": "If set to true, the protection options will be locked.", 31 | "type": "boolean" 32 | }, 33 | "DisableClearAllowedSites": { 34 | "title": "Disable Clear Allowed Sites", 35 | "description": "If set to true, the clear allowed sites button will be disabled.", 36 | "type": "boolean" 37 | }, 38 | "IgnoreFrameNavigation": { 39 | "title": "Ignore Frame Navigation", 40 | "description": "If set to true, frame navigation will be ignored.", 41 | "type": "boolean" 42 | }, 43 | "CacheExpirationSeconds": { 44 | "title": "Cache Expiration Seconds", 45 | "description": "If set, the cache expiry time will be set to this value in seconds.", 46 | "type": "integer", 47 | "minimum": 60, 48 | "maximum": 2592000, 49 | "default": 86400 50 | }, 51 | "PrecisionSecEnabled": { 52 | "title": "PrecisionSec Enabled", 53 | "description": "If set to true, PrecisionSec will be enabled.", 54 | "type": "boolean" 55 | }, 56 | "SmartScreenEnabled": { 57 | "title": "SmartScreen Enabled", 58 | "description": "If set to true, SmartScreen will be enabled.", 59 | "type": "boolean" 60 | }, 61 | "SymantecEnabled": { 62 | "title": "Symantec Enabled", 63 | "description": "If set to true, Symantec will be enabled.", 64 | "type": "boolean" 65 | }, 66 | "EmsisoftEnabled": { 67 | "title": "Emsisoft Enabled", 68 | "description": "If set to true, Emsisoft will be enabled.", 69 | "type": "boolean" 70 | }, 71 | "BitdefenderEnabled": { 72 | "title": "Bitdefender Enabled", 73 | "description": "If set to true, Bitdefender will be enabled.", 74 | "type": "boolean" 75 | }, 76 | "NortonEnabled": { 77 | "title": "Norton Enabled", 78 | "description": "If set to true, Norton will be enabled.", 79 | "type": "boolean" 80 | }, 81 | "GDATAEnabled": { 82 | "title": "G DATA Enabled", 83 | "description": "If set to true, G DATA will be enabled.", 84 | "type": "boolean" 85 | }, 86 | "CloudflareEnabled": { 87 | "title": "Cloudflare Enabled", 88 | "description": "If set to true, Cloudflare will be enabled.", 89 | "type": "boolean" 90 | }, 91 | "Quad9Enabled": { 92 | "title": "Quad9 Enabled", 93 | "description": "If set to true, Quad9 will be enabled.", 94 | "type": "boolean" 95 | }, 96 | "DNS0Enabled": { 97 | "title": "DNS0 Enabled", 98 | "description": "If set to true, DNS0 will be enabled.", 99 | "type": "boolean" 100 | }, 101 | "CleanBrowsingEnabled": { 102 | "title": "CleanBrowsing Enabled", 103 | "description": "If set to true, CleanBrowsing will be enabled.", 104 | "type": "boolean" 105 | }, 106 | "CIRAEnabled": { 107 | "title": "CIRA Enabled", 108 | "description": "If set to true, CIRA will be enabled.", 109 | "type": "boolean" 110 | }, 111 | "AdGuardEnabled": { 112 | "title": "AdGuard Enabled", 113 | "description": "If set to true, AdGuard will be enabled.", 114 | "type": "boolean" 115 | }, 116 | "SwitchCHEnabled": { 117 | "title": "Switch.ch Enabled", 118 | "description": "If set to true, Switch.ch will be enabled.", 119 | "type": "boolean" 120 | }, 121 | "CERTEEEnabled": { 122 | "title": "CERT-EE Enabled", 123 | "description": "If set to true, CERT-EE will be enabled.", 124 | "type": "boolean" 125 | }, 126 | "ControlDEnabled": { 127 | "title": "Control D Enabled", 128 | "description": "If set to true, Control D will be enabled.", 129 | "type": "boolean" 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/protection/ProtectionResult.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class ProtectionResult { 4 | /** 5 | * Constructor function for creating a browser protection result object. 6 | * @param {string} urlChecked - The URL that was checked. 7 | * @param {string} resultType - The result type of the protection check (e.g., "allowed", "malicious"). 8 | * @param {number} resultOrigin - The origin of the result (e.g., from endpoint or known top site). 9 | */ 10 | constructor(urlChecked, resultType, resultOrigin) { 11 | this.url = urlChecked; 12 | this.result = resultType; 13 | this.origin = resultOrigin; 14 | } 15 | } 16 | 17 | ProtectionResult.ResultType = { 18 | KNOWN_SAFE: "Known Safe", 19 | FAILED: "Failed", 20 | WAITING: "Waiting", 21 | ALLOWED: "Allowed", 22 | PHISHING: "Phishing", 23 | MALICIOUS: "Malicious", 24 | FRAUD: "Fraud", 25 | PUA: "Potentially Unwanted Applications", 26 | CRYPTOJACKING: "Cryptojacking", 27 | MALVERTISING: "Malvertising", 28 | SPAM: "Spam", 29 | COMPROMISED: "Compromised", 30 | UNTRUSTED: "Untrusted" 31 | }; 32 | 33 | ProtectionResult.ResultOrigin = { 34 | UNKNOWN: 0, // The result was determined via an unknown origin 35 | PRECISIONSEC: 1, // The result was determined via PrecisionSec 36 | SMARTSCREEN: 2, // The result was determined via SmartScreen 37 | SYMANTEC: 3, // The result was determined via Symantec 38 | EMSISOFT: 4, // The result was determined via Emsisoft 39 | BITDEFENDER: 5, // The result was determined via Bitdefender 40 | NORTON: 6, // The result was determined via Norton 41 | G_DATA: 7, // The result was determined via G DATA 42 | CLOUDFLARE: 8, // The result was determined via Cloudflare 43 | QUAD9: 9, // The result was determined via Quad9 44 | DNS0: 10, // The result was determined via DNS0 45 | CLEANBROWSING: 11, // The result was determined via CleanBrowsing 46 | CIRA: 12, // The result was determined via CIRA 47 | ADGUARD: 13, // The result was determined via AdGuard 48 | SWITCH_CH: 14, // The result was determined via Switch.ch 49 | CERT_EE: 15, // The result was determined via CERT-EE 50 | CONTROL_D: 16, // The result was determined via Control D 51 | }; 52 | 53 | ProtectionResult.ResultOriginNames = { 54 | 0: "Unknown", 55 | 1: "PrecisionSec Web Protection", 56 | 2: "Microsoft SmartScreen", 57 | 3: "Symantec Browser Protection", 58 | 4: "Emsisoft Web Protection", 59 | 5: "Bitdefender TrafficLight", 60 | 6: "Norton SafeWeb", 61 | 7: "G DATA WebProtection", 62 | 8: "Cloudflare Security DNS", 63 | 9: "Quad9 Security DNS", 64 | 10: "DNS0.eu Security DNS", 65 | 11: "CleanBrowsing Security DNS", 66 | 12: "CIRA Canadian Shield DNS", 67 | 13: "AdGuard Security DNS", 68 | 14: "Switch.ch Security DNS", 69 | 15: "CERT-EE Security DNS", 70 | 16: "Control D Security DNS" 71 | }; 72 | 73 | ProtectionResult.ShortOriginNames = { 74 | 0: "Unknown", 75 | 1: "PrecisionSec", 76 | 2: "SmartScreen", 77 | 3: "Symantec", 78 | 4: "Emsisoft", 79 | 5: "Bitdefender", 80 | 6: "Norton", 81 | 7: "G DATA", 82 | 8: "Cloudflare", 83 | 9: "Quad9", 84 | 10: "DNS0", 85 | 11: "CleanBrowsing", 86 | 12: "CIRA", 87 | 13: "AdGuard", 88 | 14: "Switch.ch", 89 | 15: "CERT-EE", 90 | 16: "Control D" 91 | }; 92 | 93 | ProtectionResult.CacheOriginNames = { 94 | 0: "unknown", 95 | 1: "precisionSec", 96 | 2: "smartScreen", 97 | 3: "symantec", 98 | 4: "emsisoft", 99 | 5: "bitdefender", 100 | 6: "norton", 101 | 7: "gData", 102 | 8: "cloudflare", 103 | 9: "quad9", 104 | 10: "dns0", 105 | 11: "cleanBrowsing", 106 | 12: "cira", 107 | 13: "adGuard", 108 | 14: "switchCH", 109 | 15: "certEE", 110 | 16: "controlD" 111 | }; 112 | -------------------------------------------------------------------------------- /src/main/util/CacheManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Manages the cache for the allowed protection providers. 4 | // Updated to attach a tab ID (int) to each entry in the processing queue. 5 | class CacheManager { 6 | constructor(allowedKey = 'allowedCache', processingKey = 'processingCache', debounceDelay = 5000) { 7 | Settings.get(settings => { 8 | this.expirationTime = settings.cacheExpirationSeconds; 9 | this.allowedKey = allowedKey; 10 | this.processingKey = processingKey; 11 | this.debounceDelay = debounceDelay; 12 | this.timeoutId = null; 13 | 14 | const providers = [ 15 | "precisionSec", "smartScreen", "symantec", "emsisoft", "bitdefender", 16 | "norton", "gData", "cloudflare", "quad9", "dns0", "cleanBrowsing", 17 | "cira", "adGuard", "switchCH", "certEE", "controlD", 18 | ]; 19 | 20 | this.allowedCaches = {}; 21 | this.processingCaches = {}; 22 | 23 | providers.forEach(name => { 24 | this.allowedCaches[name] = new Map(); 25 | this.processingCaches[name] = new Map(); 26 | }); 27 | 28 | // Load allowed caches (without tabId) from local storage 29 | Storage.getFromLocalStore(this.allowedKey, storedAllowed => { 30 | if (!storedAllowed) { 31 | return; 32 | } 33 | 34 | Object.keys(this.allowedCaches).forEach(name => { 35 | if (storedAllowed[name]) { 36 | this.allowedCaches[name] = new Map(Object.entries(storedAllowed[name])); 37 | } 38 | }); 39 | }); 40 | 41 | // Load processing caches (with tabId) from session storage 42 | Storage.getFromSessionStore(this.processingKey, storedProcessing => { 43 | if (!storedProcessing) { 44 | return; 45 | } 46 | 47 | Object.keys(this.processingCaches).forEach(name => { 48 | if (storedProcessing[name]) { 49 | this.processingCaches[name] = new Map(Object.entries(storedProcessing[name])); 50 | } 51 | }); 52 | }); 53 | }); 54 | } 55 | 56 | /** 57 | * Update the allowed caches in localStorage. 58 | * 59 | * @param debounced - If true, updates will be debounced to avoid frequent writes. 60 | */ 61 | updateLocalStorage(debounced) { 62 | const write = () => { 63 | const out = {}; 64 | 65 | Object.keys(this.allowedCaches).forEach(name => { 66 | out[name] = Object.fromEntries(this.allowedCaches[name]); 67 | }); 68 | 69 | Storage.setToLocalStore(this.allowedKey, out); 70 | }; 71 | 72 | if (debounced) { 73 | if (!this.timeoutId) { 74 | this.timeoutId = setTimeout(() => { 75 | this.timeoutId = null; 76 | write(); 77 | }, this.debounceDelay); 78 | } 79 | } else { 80 | write(); 81 | } 82 | } 83 | 84 | /** 85 | * Update the processing caches in sessionStorage. 86 | * 87 | * @param debounced - If true, updates will be debounced to avoid frequent writes. 88 | */ 89 | updateSessionStorage(debounced) { 90 | const write = () => { 91 | const out = {}; 92 | 93 | Object.keys(this.processingCaches).forEach(name => { 94 | out[name] = Object.fromEntries(this.processingCaches[name]); 95 | }); 96 | 97 | Storage.setToSessionStore(this.processingKey, out); 98 | }; 99 | 100 | if (debounced) { 101 | if (!this.timeoutId) { 102 | this.timeoutId = setTimeout(() => { 103 | this.timeoutId = null; 104 | write(); 105 | }, this.debounceDelay); 106 | } 107 | } else { 108 | write(); 109 | } 110 | } 111 | 112 | /** 113 | * Clears all allowed caches. 114 | */ 115 | clearAllowedCache() { 116 | Object.values(this.allowedCaches).forEach(m => m.clear()); 117 | this.updateLocalStorage(false); 118 | } 119 | 120 | /** 121 | * Clears all processing caches. 122 | */ 123 | clearProcessingCache() { 124 | Object.values(this.processingCaches).forEach(m => m.clear()); 125 | this.updateSessionStorage(false); 126 | } 127 | 128 | /** 129 | * Cleans up expired entries from both allowed and processing caches. 130 | * 131 | * @returns {number} - The number of expired entries removed from both caches. 132 | */ 133 | cleanExpiredEntries() { 134 | const now = Date.now(); 135 | let removed = 0; 136 | 137 | const cleanGroup = (group, onDirty) => { 138 | Object.values(group).forEach(map => { 139 | for (const [key, value] of map.entries()) { 140 | const expTime = (typeof value === 'number') ? value : value.exp; 141 | 142 | if (expTime < now) { 143 | map.delete(key); 144 | removed++; 145 | } 146 | } 147 | }); 148 | 149 | if (removed > 0) { 150 | onDirty(true); 151 | } 152 | }; 153 | 154 | cleanGroup(this.allowedCaches, () => this.updateLocalStorage(true)); 155 | cleanGroup(this.processingCaches, () => this.updateSessionStorage(true)); 156 | return removed; 157 | } 158 | 159 | /** 160 | * Normalizes a URL by removing the trailing slash and normalizing the hostname. 161 | * 162 | * @param url {string|URL} - The URL to normalize, can be a string or a URL object. 163 | * @returns {string|string} - The normalized URL as a string. 164 | */ 165 | normalizeUrl(url) { 166 | const u = typeof url === "string" ? new URL(url) : url; 167 | let norm = UrlHelpers.normalizeHostname(u.hostname + u.pathname); 168 | return norm.endsWith("/") ? norm.slice(0, -1) : norm; 169 | } 170 | 171 | /** 172 | * Checks if a URL is in the allowed cache for a specific provider. 173 | * 174 | * @param url {string|URL} - The URL to check, can be a string or a URL object. 175 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 176 | * @returns {boolean} - Returns true if the URL is in the allowed cache and not expired, false otherwise. 177 | */ 178 | isUrlInAllowedCache(url, name) { 179 | try { 180 | const key = this.normalizeUrl(url); 181 | const map = this.allowedCaches[name]; 182 | 183 | if (!map) { 184 | return false; 185 | } 186 | 187 | if (map.has(key)) { 188 | const exp = map.get(key); 189 | 190 | if (exp > Date.now()) { 191 | return true; 192 | } 193 | 194 | map.delete(key); 195 | this.updateLocalStorage(true); 196 | } 197 | } catch (e) { 198 | console.error(e); 199 | } 200 | return false; 201 | } 202 | 203 | /** 204 | * Checks if a string is in the allowed cache for a specific provider. 205 | * 206 | * @param str {string} - The string to check. 207 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 208 | * @returns {boolean} - Returns true if the string is in the allowed cache and not expired, false otherwise. 209 | */ 210 | isStringInAllowedCache(str, name) { 211 | try { 212 | const map = this.allowedCaches[name]; 213 | 214 | if (!map) { 215 | return false; 216 | } 217 | 218 | if (map.has(str)) { 219 | const exp = map.get(str); 220 | 221 | if (exp > Date.now()) { 222 | return true; 223 | } 224 | 225 | map.delete(str); 226 | this.updateLocalStorage(true); 227 | } 228 | } catch (e) { 229 | console.error(e); 230 | } 231 | return false; 232 | } 233 | 234 | /** 235 | * Add a URL to the allowed cache for a specific provider. 236 | * 237 | * @param url {string|URL} - The URL to add, can be a string or a URL object. 238 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 239 | */ 240 | addUrlToAllowedCache(url, name) { 241 | try { 242 | const key = this.normalizeUrl(url); 243 | const expTime = Date.now() + this.expirationTime * 1000; 244 | 245 | if (this.cleanExpiredEntries() === 0) { 246 | this.updateLocalStorage(true); 247 | } 248 | 249 | if (name === "all") { 250 | Object.values(this.allowedCaches).forEach(m => m.set(key, expTime)); 251 | } else if (this.allowedCaches[name]) { 252 | this.allowedCaches[name].set(key, expTime); 253 | } else { 254 | console.warn(`Cache "${name}" not found`); 255 | } 256 | } catch (e) { 257 | console.error(e); 258 | } 259 | } 260 | 261 | /** 262 | * Add a string key to the allowed cache for a specific provider. 263 | * 264 | * @param str {string} - The string to add. 265 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 266 | */ 267 | addStringToAllowedCache(str, name) { 268 | try { 269 | const expTime = Date.now() + this.expirationTime * 1000; 270 | 271 | if (this.cleanExpiredEntries() === 0) { 272 | this.updateLocalStorage(true); 273 | } 274 | 275 | if (name === "all") { 276 | Object.values(this.allowedCaches).forEach(m => m.set(str, expTime)); 277 | } else if (this.allowedCaches[name]) { 278 | this.allowedCaches[name].set(str, expTime); 279 | } else { 280 | console.warn(`Cache "${name}" not found`); 281 | } 282 | } catch (e) { 283 | console.error(e); 284 | } 285 | } 286 | 287 | /** 288 | * Remove a URL from the allowed cache for a specific provider. 289 | * 290 | * @param url {string|URL} - The URL to remove, can be a string or a URL object. 291 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 292 | */ 293 | removeUrlFromAllowedCache(url, name) { 294 | try { 295 | const key = this.normalizeUrl(url); 296 | 297 | if (name === "all") { 298 | Object.values(this.allowedCaches).forEach(m => m.delete(key)); 299 | } else if (this.allowedCaches[name]) { 300 | this.allowedCaches[name].delete(key); 301 | } else { 302 | console.warn(`Cache "${name}" not found`); 303 | } 304 | 305 | this.updateLocalStorage(true); 306 | } catch (e) { 307 | console.error(e); 308 | } 309 | } 310 | 311 | /** 312 | * Remove a string key from the allowed cache for a specific provider. 313 | * 314 | * @param str {string} - The string to remove. 315 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 316 | */ 317 | removeStringFromAllowedCache(str, name) { 318 | try { 319 | if (name === "all") { 320 | Object.values(this.allowedCaches).forEach(m => m.delete(str)); 321 | } else if (this.allowedCaches[name]) { 322 | this.allowedCaches[name].delete(str); 323 | } else { 324 | console.warn(`Cache "${name}" not found`); 325 | } 326 | 327 | this.updateLocalStorage(true); 328 | } catch (e) { 329 | console.error(e); 330 | } 331 | } 332 | 333 | /** 334 | * Checks if a URL is in the processing cache for a specific provider. 335 | * 336 | * @param url {string|URL} - The URL to check, can be a string or a URL object. 337 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 338 | * @returns {boolean} - Returns true if the URL is in the processing cache and not expired, false otherwise. 339 | */ 340 | isUrlInProcessingCache(url, name) { 341 | try { 342 | const key = this.normalizeUrl(url); 343 | const map = this.processingCaches[name]; 344 | 345 | if (!map) { 346 | return false; 347 | } 348 | 349 | if (map.has(key)) { 350 | const entry = map.get(key); 351 | 352 | if (entry.exp > Date.now()) { 353 | return true; 354 | } 355 | 356 | map.delete(key); 357 | this.updateSessionStorage(true); 358 | } 359 | } catch (e) { 360 | console.error(e); 361 | } 362 | return false; 363 | } 364 | 365 | /** 366 | * Checks if a string is in the processing cache for a specific provider. 367 | * 368 | * @param str {string} - The string to check. 369 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 370 | * @returns {boolean} - Returns true if the string is in the processing cache and not expired, false otherwise. 371 | */ 372 | isStringInProcessingCache(str, name) { 373 | try { 374 | const map = this.processingCaches[name]; 375 | 376 | if (!map) { 377 | return false; 378 | } 379 | 380 | if (map.has(str)) { 381 | const entry = map.get(str); 382 | 383 | if (entry.exp > Date.now()) { 384 | return true; 385 | } 386 | 387 | map.delete(str); 388 | this.updateSessionStorage(true); 389 | } 390 | } catch (e) { 391 | console.error(e); 392 | } 393 | return false; 394 | } 395 | 396 | /** 397 | * Add a URL to the processing cache, associating it with a specific tabId. 398 | * 399 | * @param {string|URL} url - The URL to add, can be a string or a URL object. 400 | * @param {string} name - The name of the provider (e.g., "precisionSec", "smartScreen"). 401 | * @param {number} tabId - The ID of the tab associated with this URL. 402 | */ 403 | addUrlToProcessingCache(url, name, tabId) { 404 | try { 405 | const key = this.normalizeUrl(url); 406 | const expTime = Date.now() + this.expirationTime * 1000; 407 | 408 | if (this.cleanExpiredEntries() === 0) { 409 | this.updateSessionStorage(true); 410 | } 411 | 412 | const entry = {exp: expTime, tabId: tabId}; 413 | 414 | if (name === "all") { 415 | Object.values(this.processingCaches).forEach(m => m.set(key, entry)); 416 | } else if (this.processingCaches[name]) { 417 | this.processingCaches[name].set(key, entry); 418 | } else { 419 | console.warn(`Processing cache "${name}" not found`); 420 | } 421 | } catch (e) { 422 | console.error(e); 423 | } 424 | } 425 | 426 | /** 427 | * Add a string key to the processing cache, associating it with a specific tabId. 428 | * 429 | * @param {string} str - The string to add. 430 | * @param {string} name - The name of the provider (e.g., "precisionSec", "smartScreen"). 431 | * @param {number} tabId - The ID of the tab associated with this string. 432 | */ 433 | addStringToProcessingCache(str, name, tabId) { 434 | try { 435 | const expTime = Date.now() + this.expirationTime * 1000; 436 | 437 | if (this.cleanExpiredEntries() === 0) { 438 | this.updateSessionStorage(true); 439 | } 440 | 441 | const entry = {exp: expTime, tabId: tabId}; 442 | 443 | if (name === "all") { 444 | Object.values(this.processingCaches).forEach(m => m.set(str, entry)); 445 | } else if (this.processingCaches[name]) { 446 | this.processingCaches[name].set(str, entry); 447 | } else { 448 | console.warn(`Processing cache "${name}" not found`); 449 | } 450 | } catch (e) { 451 | console.error(e); 452 | } 453 | } 454 | 455 | /** 456 | * Remove a URL from the processing cache for a specific provider. 457 | * 458 | * @param url {string|URL} - The URL to remove, can be a string or a URL object. 459 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 460 | */ 461 | removeUrlFromProcessingCache(url, name) { 462 | try { 463 | const key = this.normalizeUrl(url); 464 | 465 | if (name === "all") { 466 | Object.values(this.processingCaches).forEach(m => m.delete(key)); 467 | } else if (this.processingCaches[name]) { 468 | this.processingCaches[name].delete(key); 469 | } else { 470 | console.warn(`Processing cache "${name}" not found`); 471 | } 472 | 473 | this.updateSessionStorage(true); 474 | } catch (e) { 475 | console.error(e); 476 | } 477 | } 478 | 479 | /** 480 | * Remove a string key from the processing cache for a specific provider. 481 | * 482 | * @param str {string} - The string to remove. 483 | * @param name {string} - The name of the provider (e.g., "precisionSec", "smartScreen"). 484 | */ 485 | removeStringFromProcessingCache(str, name) { 486 | try { 487 | if (name === "all") { 488 | Object.values(this.processingCaches).forEach(m => m.delete(str)); 489 | } else if (this.processingCaches[name]) { 490 | this.processingCaches[name].delete(str); 491 | } else { 492 | console.warn(`Processing cache "${name}" not found`); 493 | } 494 | 495 | this.updateSessionStorage(true); 496 | } catch (e) { 497 | console.error(e); 498 | } 499 | } 500 | 501 | /** 502 | * Retrieve all normalized-URL keys (or string keys) in the processing cache for a given provider 503 | * that are associated with the specified tabId and not yet expired. 504 | * 505 | * @param {string} name - The name of the provider (e.g., "precisionSec", "smartScreen"). 506 | * @param {number} tabId - The ID of the tab to filter by. 507 | * @returns {string[]} - An array of keys (normalized URLs or strings) that match the criteria. 508 | */ 509 | getKeysByTabId(name, tabId) { 510 | const results = []; 511 | const map = this.processingCaches[name]; 512 | 513 | if (!map) { 514 | return results; 515 | } 516 | 517 | const now = Date.now(); 518 | 519 | for (const [key, entry] of map.entries()) { 520 | if (entry.tabId === tabId) { 521 | if (entry.exp > now) { 522 | results.push(key); 523 | } else { 524 | // expired: remove it 525 | map.delete(key); 526 | } 527 | } 528 | } 529 | 530 | // If any expired entries were removed, persist the change 531 | this.updateSessionStorage(true); 532 | return results; 533 | } 534 | 535 | /** 536 | * Remove all entries in the processing cache for all keys associated with a specific tabId. 537 | * 538 | * @param tabId - The ID of the tab whose entries should be removed. 539 | */ 540 | removeKeysByTabId(tabId) { 541 | let removedCount = 0; 542 | 543 | Object.keys(this.processingCaches).forEach(name => { 544 | const map = this.processingCaches[name]; 545 | 546 | if (!map) { 547 | return; 548 | } 549 | 550 | for (const [key, entry] of map.entries()) { 551 | if (entry.tabId === tabId) { 552 | removedCount++; 553 | map.delete(key); 554 | } 555 | } 556 | }); 557 | 558 | // Persist the changes to session storage 559 | if (removedCount > 0) { 560 | console.debug(`Removed ${removedCount} entries from processing cache for tab ID ${tabId}`); 561 | this.updateSessionStorage(false); 562 | } 563 | } 564 | } 565 | -------------------------------------------------------------------------------- /src/main/util/MessageType.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Messages = { 4 | MessageType: { 5 | CONTINUE_TO_SAFETY: "continueToSafety", 6 | CONTINUE_TO_SITE: "continueToSite", 7 | REPORT_SITE: "reportSite", 8 | ALLOW_SITE: "allowSite", 9 | 10 | // Protection provider toggles 11 | PRECISIONSEC_TOGGLED: "precisionSecToggled", 12 | SMARTSCREEN_TOGGLED: "smartScreenToggled", 13 | SYMANTEC_TOGGLED: "symantecToggled", 14 | EMSISOFT_TOGGLED: "emsisoftToggled", 15 | BITDEFENDER_TOGGLED: "bitdefenderToggled", 16 | NORTON_TOGGLED: "nortonToggled", 17 | G_DATA_TOGGLED: "gDataToggled", 18 | CLOUDFLARE_TOGGLED: "cloudflareToggled", 19 | QUAD9_TOGGLED: "quad9Toggled", 20 | DNS0_TOGGLED: "dns0Toggled", 21 | CLEAN_BROWSING_TOGGLED: "cleanBrowsingToggled", 22 | CIRA_TOGGLED: "ciraToggled", 23 | ADGUARD_TOGGLED: "adGuardToggled", 24 | SWITCH_CH_TOGGLED: "switchCHToggled", 25 | CERT_EE_TOGGLED: "certEEToggled", 26 | CONTROL_D_TOGGLED: "controlDToggled", 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/main/util/Settings.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Manages user preferences and configurations. 4 | const Settings = (function () { 5 | // Key for storing settings in local storage 6 | const settingsKey = "Settings"; 7 | 8 | let defaultSettings = { 9 | precisionSecEnabled: true, // Default state for PrecisionSec 10 | smartScreenEnabled: true, // Default state for SmartScreen 11 | symantecEnabled: false, // Default state for Symantec 12 | emsisoftEnabled: true, // Default state for Emsisoft 13 | bitdefenderEnabled: true, // Default state for Bitdefender 14 | nortonEnabled: true, // Default state for Norton 15 | gDataEnabled: true, // Default state for G DATA 16 | cloudflareEnabled: false, // Default state for Cloudflare 17 | quad9Enabled: true, // Default state for Quad9 18 | dns0Enabled: false, // Default state for DNS0 19 | cleanBrowsingEnabled: false, // Default state for CleanBrowsing 20 | ciraEnabled: false, // Default state for CIRA 21 | adGuardEnabled: true, // Default state for AdGuard 22 | switchCHEnabled: true, // Default state for Switch.ch 23 | certEEEnabled: true, // Default state for CERT-EE 24 | controlDEnabled: true, // Default state for Control D 25 | 26 | notificationsEnabled: false, // Default state for notifications 27 | ignoreFrameNavigation: true, // Default state for ignoring frame navigation 28 | hideContinueButtons: false, // Default state for hiding continue buttons 29 | hideReportButton: false, // Default state for hiding the report button 30 | cacheExpirationSeconds: 86400, // Default cache expiration time in seconds 31 | }; 32 | 33 | /** 34 | * Compares two objects and updates the target object with values from the source object if they differ. 35 | * @param {Object} target - The target object to update. 36 | * @param {Object} source - The source object to compare with. 37 | * @returns {boolean} - Returns true if any values were updated, false otherwise. 38 | */ 39 | const updateIfChanged = function (target, source) { 40 | let hasChanges = false; 41 | 42 | if (source) { 43 | // Iterate through the source object properties 44 | for (let key in source) { 45 | // If the values differ, update the target and mark changes 46 | if (source[key] !== target[key]) { 47 | target[key] = source[key]; 48 | hasChanges = true; 49 | } 50 | } 51 | } 52 | return hasChanges; // Return whether any changes were made 53 | }; 54 | 55 | return { 56 | /** 57 | * Retrieves settings from local storage and merges them with default settings. 58 | * @param {Function} callback - The function to call with the retrieved settings. 59 | */ 60 | get: function (callback) { 61 | Storage.getFromLocalStore(settingsKey, function (storedSettings) { 62 | // Clone the default settings object 63 | let mergedSettings = JSON.parse(JSON.stringify(defaultSettings)); 64 | 65 | // Merge any stored settings into the cloned default settings 66 | updateIfChanged(mergedSettings, storedSettings); 67 | 68 | // Invoke the callback with the merged settings 69 | callback && callback(mergedSettings); 70 | }); 71 | }, 72 | 73 | /** 74 | * Saves settings to local storage, merging them with any previously stored settings. 75 | * @param {Object} newSettings - The new settings to save. 76 | * @param {Function} [callback] - Optional callback to call after settings are saved. 77 | */ 78 | set: function (newSettings, callback) { 79 | Storage.getFromLocalStore(settingsKey, function (storedSettings) { 80 | // Clone the default settings object 81 | let mergedSettings = JSON.parse(JSON.stringify(defaultSettings)); 82 | 83 | // Merge stored settings and new settings into the cloned default settings 84 | storedSettings && updateIfChanged(mergedSettings, storedSettings); 85 | updateIfChanged(mergedSettings, newSettings); 86 | 87 | // Save the merged settings back to local storage 88 | Storage.setToLocalStore(settingsKey, mergedSettings, callback); 89 | }); 90 | } 91 | }; 92 | })(); 93 | -------------------------------------------------------------------------------- /src/main/util/Storage.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Storage utility for interacting with the browser's local storage. 4 | const Storage = { 5 | 6 | /** 7 | * Retrieves data from the browser's local storage. 8 | * @param {string} key - The key to retrieve from local storage. 9 | * @param {Function} callback - The function to call with the retrieved value. 10 | */ 11 | getFromLocalStore: function (key, callback) { 12 | // Browser API compatibility between Chrome and Firefox 13 | const browserAPI = typeof browser === 'undefined' ? chrome : browser; 14 | 15 | /** 16 | * Internal function to handle the retrieval process from local storage. 17 | * @param {object} storage - The storage object (browserAPI.storage.local). 18 | * @param {string} key - The key to retrieve from the storage. 19 | * @param {Function} callback - The function to call with the retrieved value. 20 | */ 21 | (function (storage, key, callback) { 22 | // Get the data from local storage. 23 | storage.get(key, function (result) { 24 | // Extract the value associated with the key. 25 | let value = result && result[key]; 26 | 27 | // Call the callback function with the retrieved value. 28 | callback(value); 29 | }); 30 | })(browserAPI.storage.local, key, callback); 31 | }, 32 | 33 | /** 34 | * Saves data to the browser's local storage. 35 | * @param {string} key - The key to save to local storage. 36 | * @param {any} value - The value to store. 37 | * @param {Function} [callback] - Optional callback to call after saving. 38 | */ 39 | setToLocalStore: function (key, value, callback) { 40 | // Browser API compatibility between Chrome and Firefox 41 | const browserAPI = typeof browser === 'undefined' ? chrome : browser; 42 | 43 | /** 44 | * Internal function to handle the saving process to local storage. 45 | * @param {object} storage - The storage object (browserAPI.storage.local). 46 | * @param {string} key - The key to save the value under. 47 | * @param {any} value - The value to store. 48 | * @param {Function} [callback] - Optional callback to call after saving. 49 | */ 50 | (function (storage, key, value, callback) { 51 | // Create an object to hold the key-value pair. 52 | let data = {}; 53 | data[key] = value; 54 | 55 | // Save the data to local storage. 56 | storage.set(data, callback); 57 | })(browserAPI.storage.local, key, value, callback); 58 | }, 59 | 60 | /** 61 | * Retrieves data from the browser's session storage. 62 | * @param {string} key - The key to retrieve from session storage. 63 | * @param {Function} callback - The function to call with the retrieved value. 64 | */ 65 | getFromSessionStore: function (key, callback) { 66 | // Browser API compatibility between Chrome and Firefox 67 | const browserAPI = typeof browser === 'undefined' ? chrome : browser; 68 | 69 | /** 70 | * Internal function to handle the retrieval process from session storage. 71 | * @param {object} storage - The storage object (browserAPI.storage.session). 72 | * @param {string} key - The key to retrieve from the storage. 73 | * @param {Function} callback - The function to call with the retrieved value. 74 | */ 75 | (function (storage, key, callback) { 76 | // Get the data from session storage. 77 | storage.get(key, function (result) { 78 | // Extract the value associated with the key. 79 | let value = result && result[key]; 80 | 81 | // Call the callback function with the retrieved value. 82 | callback(value); 83 | }); 84 | })(browserAPI.storage.session, key, callback); 85 | }, 86 | 87 | /** 88 | * Saves data to the browser's session storage. 89 | * @param {string} key - The key to save to session storage. 90 | * @param {any} value - The value to store. 91 | * @param {Function} [callback] - Optional callback to call after saving. 92 | */ 93 | setToSessionStore: function (key, value, callback) { 94 | // Browser API compatibility between Chrome and Firefox 95 | const browserAPI = typeof browser === 'undefined' ? chrome : browser; 96 | 97 | /** 98 | * Internal function to handle the saving process to session storage. 99 | * @param {object} storage - The storage object (browserAPI.storage.session). 100 | * @param {string} key - The key to save the value under. 101 | * @param {any} value - The value to store. 102 | * @param {Function} [callback] - Optional callback to call after saving. 103 | */ 104 | (function (storage, key, value, callback) { 105 | // Create an object to hold the key-value pair. 106 | let data = {}; 107 | data[key] = value; 108 | 109 | // Save the data to session storage. 110 | storage.set(data, callback); 111 | })(browserAPI.storage.session, key, value, callback); 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /src/main/util/UrlHelpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Object containing helper functions for working with URLs. 4 | const UrlHelpers = { 5 | 6 | /** 7 | * Extracts the blocked URL (the site being reported as malicious) from the query parameters of a URL. 8 | * @param {string} url - The URL containing the blocked site information. 9 | * @returns {string|null} - The blocked URL, or null if not found. 10 | */ 11 | extractBlockedUrl: url => new URL(url).searchParams.get("url"), 12 | 13 | /** 14 | * Extracts the origin of the protection result from the query parameters of a URL. 15 | * @param url - The URL containing the origin information 16 | * @returns {string} - The origin of the protection result 17 | */ 18 | extractOrigin: url => new URL(url).searchParams.get("or"), 19 | 20 | /** 21 | * Extracts the result (e.g., phishing, malware) from the query parameters of a URL. 22 | * 23 | * @param {string} url - The URL containing the result. 24 | * @returns {string|null} - The result from the URL, or null if not found. 25 | */ 26 | extractResult: url => new URL(url).searchParams.get("rs"), 27 | 28 | /** 29 | * Constructs the URL for the browser's block page, which shows a warning when a site is blocked. 30 | * @param {object} protectionResult - The result object containing details about the threat. 31 | * @returns {string} - The full URL for the block page. 32 | */ 33 | getBlockPageUrl: (protectionResult) => { 34 | // Browser API compatibility between Chrome and Firefox 35 | const browserAPI = typeof browser === 'undefined' ? chrome : browser; 36 | 37 | // Base URL for the block page 38 | const blockPageBaseUrl = browserAPI.runtime.getURL("pages/warning/WarningPage.html"); 39 | 40 | // Determine the result from the protection result object 41 | const result = protectionResult.result; 42 | 43 | // Construct a new URL object for the block page 44 | let blockPageUrl = new URL(blockPageBaseUrl); 45 | 46 | // Set the search parameters for the block page URL 47 | blockPageUrl.search = new URLSearchParams([ 48 | ["url", protectionResult.url], // The URL of the blocked site 49 | ["or", protectionResult.origin], // The origin of the protection result 50 | ["rs", result] // The result string (e.g. Malicious) 51 | ]).toString(); 52 | 53 | // Return the constructed block page URL as a string 54 | return blockPageUrl.toString(); 55 | }, 56 | 57 | /** 58 | * Normalizes a hostname by removing "www." if it exists. 59 | * @param {string} hostname - The hostname to normalize. 60 | * @returns {string} - The normalized hostname. 61 | */ 62 | normalizeHostname: hostname => { 63 | // Ensure the hostname is a string before proceeding 64 | if (typeof hostname !== 'string') { 65 | return ''; 66 | } 67 | 68 | // Remove "www." prefix if it exists in the hostname 69 | if (hostname.toLowerCase().startsWith("www.")) { 70 | hostname = hostname.substring(4); 71 | } 72 | return hostname; 73 | } 74 | }; 75 | -------------------------------------------------------------------------------- /src/main/util/hashing/MD5.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | !function (input) { 4 | 5 | /** 6 | * Adds two 32-bit integers with proper handling of carry bits. 7 | * 8 | * @param {number} a - The first integer to add. 9 | * @param {number} b - The second integer to add. 10 | * @returns {number} The resulting sum of the two integers. 11 | */ 12 | function addHash(a, b) { 13 | const lowBits = (65535 & a) + (65535 & b); 14 | return (a >> 16) + (b >> 16) + (lowBits >> 16) << 16 | 65535 & lowBits; 15 | } 16 | 17 | /** 18 | * Performs a shift-based hash computation for the input parameters using specific round constants. 19 | * 20 | * @param {number} value - The input value to hash. 21 | * @param {number} a - The first hash value. 22 | * @param {number} b - The second hash value. 23 | * @param {number} shiftAmount - The amount to shift the value. 24 | * @param {number} constant - The constant value for the shift. 25 | * @param {number} roundConst - The round constant for the hashing operation. 26 | * @returns {number} The resulting hash value. 27 | */ 28 | function computeHash(value, a, b, shiftAmount, constant, roundConst) { 29 | let sum; 30 | let shift; 31 | return addHash((sum = addHash(addHash(a, value), addHash(shiftAmount, roundConst))) << (shift = constant) | sum >>> 32 - shift, b); 32 | } 33 | 34 | /** 35 | * Executes the first round of hashing, applying a specific bitwise operation. 36 | * 37 | * @param {number} value - The input value to hash. 38 | * @param {number} a - The first hash value. 39 | * @param {number} b - The second hash value. 40 | * @param {number} c - The third hash value. 41 | * @param {number} shiftAmount - The amount to shift the value. 42 | * @param {number} constant - The constant value for the shift. 43 | * @param {number} roundConst - The round constant for the hashing operation. 44 | * @returns {number} The resulting hash value. 45 | */ 46 | function roundOne(value, a, b, c, shiftAmount, constant, roundConst) { 47 | return computeHash(a & b | ~a & c, value, a, shiftAmount, constant, roundConst); 48 | } 49 | 50 | /** 51 | * Executes the second round of hashing, applying a different bitwise operation. 52 | * 53 | * @param {number} value - The input value to hash. 54 | * @param {number} a - The first hash value. 55 | * @param {number} b - The second hash value. 56 | * @param {number} c - The third hash value. 57 | * @param {number} shiftAmount - The amount to shift the value. 58 | * @param {number} constant - The constant value for the shift. 59 | * @param {number} roundConst - The round constant for the hashing operation. 60 | * @returns {number} The resulting hash value. 61 | */ 62 | function roundTwo(value, a, b, c, shiftAmount, constant, roundConst) { 63 | return computeHash(a & c | b & ~c, value, a, shiftAmount, constant, roundConst); 64 | } 65 | 66 | /** 67 | * Executes the third round of hashing, using XOR for bitwise operations. 68 | * 69 | * @param {number} value - The input value to hash. 70 | * @param {number} a - The first hash value. 71 | * @param {number} b - The second hash value. 72 | * @param {number} c - The third hash value. 73 | * @param {number} shiftAmount - The amount to shift the value. 74 | * @param {number} constant - The constant value for the shift. 75 | * @param {number} roundConst - The round constant for the hashing operation. 76 | * @returns {number} The resulting hash value. 77 | */ 78 | function roundThree(value, a, b, c, shiftAmount, constant, roundConst) { 79 | return computeHash(a ^ b ^ c, value, a, shiftAmount, constant, roundConst); 80 | } 81 | 82 | /** 83 | * Executes the fourth round of hashing, using an OR and NOT combination for bitwise operations. 84 | * 85 | * @param {number} value - The input value to hash. 86 | * @param {number} a - The first hash value. 87 | * @param {number} b - The second hash value. 88 | * @param {number} c - The third hash value. 89 | * @param {number} shiftAmount - The amount to shift the value. 90 | * @param {number} constant - The constant value for the shift. 91 | * @param {number} roundConst - The round constant for the hashing operation. 92 | * @returns {number} The resulting hash value. 93 | */ 94 | function roundFour(value, a, b, c, shiftAmount, constant, roundConst) { 95 | return computeHash(b ^ (a | ~c), value, a, shiftAmount, constant, roundConst); 96 | } 97 | 98 | /** 99 | * Performs the core hash function by processing the data in blocks, iterating over four rounds of transformations. 100 | * 101 | * @param {Array} data - The input data to hash. 102 | * @param {number} bitLength - The length of the input data in bits. 103 | * @returns {Array} The resulting hash values. 104 | */ 105 | function hashFunction(data, bitLength) { 106 | let tempA; 107 | let tempB; 108 | let tempC; 109 | let tempD; 110 | 111 | // Padding the data and setting the bit length 112 | data[bitLength >> 5] |= 128 << bitLength % 32; 113 | data[14 + (bitLength + 64 >>> 9 << 4)] = bitLength; 114 | 115 | // Initial hash values (MD5 specific constants) 116 | let hashA = 1732584193; 117 | let hashB = -271733879; 118 | let hashC = -1732584194; 119 | let hashD = 271733878; 120 | 121 | // Process each 512-bit block (16 words of 32 bits each) 122 | for (let blockIndex = 0; blockIndex < data.length; blockIndex += 16) { 123 | tempA = hashA; 124 | tempB = hashB; 125 | tempC = hashC; 126 | tempD = hashD; 127 | 128 | // First round of hashing 129 | hashA = roundOne(hashA, hashB, hashC, hashD, data[blockIndex], 7, -680876936); 130 | hashD = roundOne(hashD, hashA, hashB, hashC, data[blockIndex + 1], 12, -389564586); 131 | hashC = roundOne(hashC, hashD, hashA, hashB, data[blockIndex + 2], 17, 606105819); 132 | hashB = roundOne(hashB, hashC, hashD, hashA, data[blockIndex + 3], 22, -1044525330); 133 | hashA = roundOne(hashA, hashB, hashC, hashD, data[blockIndex + 4], 7, -176418897); 134 | hashD = roundOne(hashD, hashA, hashB, hashC, data[blockIndex + 5], 12, 1200080426); 135 | hashC = roundOne(hashC, hashD, hashA, hashB, data[blockIndex + 6], 17, -1473231341); 136 | hashB = roundOne(hashB, hashC, hashD, hashA, data[blockIndex + 7], 22, -45705983); 137 | hashA = roundOne(hashA, hashB, hashC, hashD, data[blockIndex + 8], 7, 1770035416); 138 | hashD = roundOne(hashD, hashA, hashB, hashC, data[blockIndex + 9], 12, -1958414417); 139 | hashC = roundOne(hashC, hashD, hashA, hashB, data[blockIndex + 10], 17, -42063); 140 | hashB = roundOne(hashB, hashC, hashD, hashA, data[blockIndex + 11], 22, -1990404162); 141 | hashA = roundOne(hashA, hashB, hashC, hashD, data[blockIndex + 12], 7, 1804603682); 142 | hashD = roundOne(hashD, hashA, hashB, hashC, data[blockIndex + 13], 12, -40341101); 143 | hashC = roundOne(hashC, hashD, hashA, hashB, data[blockIndex + 14], 17, -1502002290); 144 | hashB = roundOne(hashB, hashC, hashD, hashA, data[blockIndex + 15], 22, 1236535329); 145 | 146 | // Second round of hashing 147 | hashA = roundTwo(hashA, hashB, hashC, hashD, data[blockIndex + 1], 5, -165796510); 148 | hashD = roundTwo(hashD, hashA, hashB, hashC, data[blockIndex + 6], 9, -1069501632); 149 | hashC = roundTwo(hashC, hashD, hashA, hashB, data[blockIndex + 11], 14, 643717713); 150 | hashB = roundTwo(hashB, hashC, hashD, hashA, data[blockIndex], 20, -373897302); 151 | hashA = roundTwo(hashA, hashB, hashC, hashD, data[blockIndex + 5], 5, -701558691); 152 | hashD = roundTwo(hashD, hashA, hashB, hashC, data[blockIndex + 10], 9, 38016083); 153 | hashC = roundTwo(hashC, hashD, hashA, hashB, data[blockIndex + 15], 14, -660478335); 154 | hashB = roundTwo(hashB, hashC, hashD, hashA, data[blockIndex + 4], 20, -405537848); 155 | hashA = roundTwo(hashA, hashB, hashC, hashD, data[blockIndex + 9], 5, 568446438); 156 | hashD = roundTwo(hashD, hashA, hashB, hashC, data[blockIndex + 14], 9, -1019803690); 157 | hashC = roundTwo(hashC, hashD, hashA, hashB, data[blockIndex + 3], 14, -187363961); 158 | hashB = roundTwo(hashB, hashC, hashD, hashA, data[blockIndex + 8], 20, 1163531501); 159 | hashA = roundTwo(hashA, hashB, hashC, hashD, data[blockIndex + 13], 5, -1444681467); 160 | hashD = roundTwo(hashD, hashA, hashB, hashC, data[blockIndex + 2], 9, -51403784); 161 | hashC = roundTwo(hashC, hashD, hashA, hashB, data[blockIndex + 7], 14, 1735328473); 162 | hashB = roundTwo(hashB, hashC, hashD, hashA, data[blockIndex + 12], 20, -1926607734); 163 | 164 | // Third round of hashing 165 | hashA = roundThree(hashA, hashB, hashC, hashD, data[blockIndex + 5], 4, -378558); 166 | hashD = roundThree(hashD, hashA, hashB, hashC, data[blockIndex + 8], 11, -2022574463); 167 | hashC = roundThree(hashC, hashD, hashA, hashB, data[blockIndex + 11], 16, 1839030562); 168 | hashB = roundThree(hashB, hashC, hashD, hashA, data[blockIndex + 14], 23, -35309556); 169 | hashA = roundThree(hashA, hashB, hashC, hashD, data[blockIndex + 1], 4, -1530992060); 170 | hashD = roundThree(hashD, hashA, hashB, hashC, data[blockIndex + 4], 11, 1272893353); 171 | hashC = roundThree(hashC, hashD, hashA, hashB, data[blockIndex + 7], 16, -155497632); 172 | hashB = roundThree(hashB, hashC, hashD, hashA, data[blockIndex + 10], 23, -1094730640); 173 | hashA = roundThree(hashA, hashB, hashC, hashD, data[blockIndex + 13], 4, 681279174); 174 | hashD = roundThree(hashD, hashA, hashB, hashC, data[blockIndex], 11, -358537222); 175 | hashC = roundThree(hashC, hashD, hashA, hashB, data[blockIndex + 3], 16, -722521979); 176 | hashB = roundThree(hashB, hashC, hashD, hashA, data[blockIndex + 6], 23, 76029189); 177 | hashA = roundThree(hashA, hashB, hashC, hashD, data[blockIndex + 9], 4, -640364487); 178 | hashD = roundThree(hashD, hashA, hashB, hashC, data[blockIndex + 12], 11, -421815835); 179 | hashC = roundThree(hashC, hashD, hashA, hashB, data[blockIndex + 15], 16, 530742520); 180 | hashB = roundThree(hashB, hashC, hashD, hashA, data[blockIndex + 2], 23, -995338651); 181 | 182 | // Fourth round of hashing 183 | hashA = roundFour(hashA, hashB, hashC, hashD, data[blockIndex], 6, -198630844); 184 | hashD = roundFour(hashD, hashA, hashB, hashC, data[blockIndex + 7], 10, 1126891415); 185 | hashC = roundFour(hashC, hashD, hashA, hashB, data[blockIndex + 14], 15, -1416354905); 186 | hashB = roundFour(hashB, hashC, hashD, hashA, data[blockIndex + 5], 21, -57434055); 187 | hashA = roundFour(hashA, hashB, hashC, hashD, data[blockIndex + 12], 6, 1700485571); 188 | hashD = roundFour(hashD, hashA, hashB, hashC, data[blockIndex + 3], 10, -1894986606); 189 | hashC = roundFour(hashC, hashD, hashA, hashB, data[blockIndex + 10], 15, -1051523); 190 | hashB = roundFour(hashB, hashC, hashD, hashA, data[blockIndex + 1], 21, -2054922799); 191 | hashA = roundFour(hashA, hashB, hashC, hashD, data[blockIndex + 8], 6, 1873313359); 192 | hashD = roundFour(hashD, hashA, hashB, hashC, data[blockIndex + 15], 10, -30611744); 193 | hashC = roundFour(hashC, hashD, hashA, hashB, data[blockIndex + 6], 15, -1560198380); 194 | hashB = roundFour(hashB, hashC, hashD, hashA, data[blockIndex + 13], 21, 1309151649); 195 | hashA = roundFour(hashA, hashB, hashC, hashD, data[blockIndex + 4], 6, -145523070); 196 | hashD = roundFour(hashD, hashA, hashB, hashC, data[blockIndex + 11], 10, -1120210379); 197 | hashC = roundFour(hashC, hashD, hashA, hashB, data[blockIndex + 2], 15, 718787259); 198 | hashB = roundFour(hashB, hashC, hashD, hashA, data[blockIndex + 9], 21, -343485551); 199 | 200 | // Update hash values after processing the block 201 | hashA = addHash(hashA, tempA); 202 | hashB = addHash(hashB, tempB); 203 | hashC = addHash(hashC, tempC); 204 | hashD = addHash(hashD, tempD); 205 | } 206 | return [hashA, hashB, hashC, hashD]; 207 | } 208 | 209 | /** 210 | * Converts the input array into a string representation. 211 | * 212 | * @param {Array} input - The input array to convert. 213 | * @returns {string} The string representation of the input array. 214 | */ 215 | function convertToString(input) { 216 | let result = ""; 217 | let length = 32 * input.length; 218 | 219 | for (let i = 0; i < length; i += 8) { 220 | result += String.fromCharCode(input[i >> 5] >>> i % 32 & 255); 221 | } 222 | return result; 223 | } 224 | 225 | /** 226 | * Converts the input string into an array of words (32-bit integers). 227 | * 228 | * @param {string} input - The input string to convert. 229 | * @returns {Array} The array of words (32-bit integers). 230 | */ 231 | function convertToWordArray(input) { 232 | const wordArray = []; 233 | let i; 234 | 235 | // Initialize the word array to match the required size 236 | const arrayLength = input.length >> 2; 237 | for (i = 0; i < arrayLength; i += 1) { 238 | wordArray[i] = 0; 239 | } 240 | 241 | const maxLength = 8 * input.length; 242 | 243 | // Convert input string to word array 244 | for (i = 0; i < maxLength; i += 8) { 245 | wordArray[i >> 5] |= (255 & input.charCodeAt(i / 8)) << i % 32; 246 | } 247 | return wordArray; 248 | } 249 | 250 | /** 251 | * Converts the input string into a hexadecimal string representation. 252 | * 253 | * @param {string} input - The input string to convert. 254 | * @returns {string} The hexadecimal representation of the input string. 255 | */ 256 | function convertToHexString(input) { 257 | const hexDigits = "0123456789abcdef"; 258 | let currentChar; 259 | let hexString = ""; 260 | let i; 261 | 262 | // Convert each character to its hexadecimal representation 263 | for (i = 0; i < input.length; i += 1) { 264 | currentChar = input.charCodeAt(i); 265 | hexString += hexDigits.charAt(currentChar >>> 4 & 15) + hexDigits.charAt(15 & currentChar); 266 | } 267 | return hexString; 268 | } 269 | 270 | /** 271 | * Prepares the input string by encoding it into UTF-8. 272 | * 273 | * @param input - The input string to prepare. 274 | * @returns {string} The prepared input string. 275 | */ 276 | function prepareInput(input) { 277 | return unescape(encodeURIComponent(input)) 278 | } 279 | 280 | /** 281 | * Generates the MD5 hash for the given input string. 282 | * 283 | * @param input - The input string to hash. 284 | * @returns {string} The MD5 hash of the input string. 285 | */ 286 | function generateHash(input) { 287 | let wordArray = prepareInput(input); 288 | return convertToString(hashFunction(convertToWordArray(wordArray), 8 * wordArray.length)); 289 | } 290 | 291 | /** 292 | * Generates an HMAC hash using the input message and key, applying the MD5 hash algorithm. 293 | * 294 | * @param input - The input string to hash. 295 | * @param key - The key to use for hashing. 296 | * @returns {string} The HMAC hash of the input string. 297 | */ 298 | function hmacHash(input, key) { 299 | return function (message, key) { 300 | let innerPadding = []; 301 | let outerPadding = []; 302 | let messageWordArray = convertToWordArray(message); 303 | 304 | // Initialize padding 305 | if (messageWordArray.length > 16) { 306 | messageWordArray = hashFunction(messageWordArray, 8 * message.length); 307 | } 308 | for (let i = 0; i < 16; i += 1) { 309 | innerPadding[i] = 0x5C5C5C5C ^ messageWordArray[i]; 310 | outerPadding[i] = 0x36363636 ^ messageWordArray[i]; 311 | } 312 | 313 | let innerHash = hashFunction(innerPadding.concat(convertToWordArray(key)), 512 + 8 * key.length); 314 | return convertToString(hashFunction(outerPadding.concat(innerHash), 640)); 315 | }(prepareInput(input), prepareInput(key)) 316 | } 317 | 318 | /** 319 | * Returns the hashed output in the desired format, either as a raw string or hexadecimal string. 320 | * 321 | * @param input - The input string to hash. 322 | * @param key - The key to use for hashing. 323 | * @param asHex - A flag indicating whether to return the hash as a hexadecimal string. 324 | * @returns {string} The hashed output in the desired format. 325 | */ 326 | function getHashedOutput(input, key, asHex) { 327 | if (key) { 328 | return asHex ? hmacHash(key, input) : convertToHexString(hmacHash(key, input)); 329 | } else { 330 | return asHex ? generateHash(input) : convertToHexString(generateHash(input)); 331 | } 332 | } 333 | 334 | /** 335 | * Exposes the MD5 hash function to the global scope. 336 | */ 337 | if (typeof define === "function" && define.amd) { 338 | define(() => getHashedOutput); 339 | } else if (typeof module === "object" && module.exports) { 340 | module.exports = getHashedOutput; 341 | } else { 342 | input.MD5 = getHashedOutput; 343 | } 344 | }(this); 345 | -------------------------------------------------------------------------------- /src/main/util/hashing/RC4.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * RC4 Stream Cipher Implementation 5 | * 6 | * This function encrypts or decrypts input data using the RC4 algorithm 7 | * and a given key. The RC4 algorithm is a stream cipher that generates a 8 | * pseudo-random key-stream based on the key, which is then XORed with the 9 | * input data to produce the output. 10 | * 11 | * @param {string} key - The secret key used for encryption or decryption. 12 | * @param {string} input - The input string to be encrypted or decrypted. 13 | * @returns {string} The resulting encrypted or decrypted string. 14 | */ 15 | function RC4(key, input) { 16 | 17 | const stateArray = []; 18 | let keyIndex = 0; 19 | let output = ''; 20 | 21 | // Initialize the state array 22 | for (let i = 0; i < 256; i++) { 23 | stateArray[i] = i; 24 | } 25 | 26 | // Key-scheduling algorithm 27 | for (let i = 0; i < 256; i++) { 28 | keyIndex = (keyIndex + stateArray[i] + key.charCodeAt(i % key.length)) % 256; 29 | [stateArray[i], stateArray[keyIndex]] = [stateArray[keyIndex], stateArray[i]]; // Swap values 30 | } 31 | 32 | // Pseudo-random generation algorithm 33 | let i = 0; 34 | keyIndex = 0; 35 | 36 | // Encrypt or decrypt the input data 37 | for (let d = 0; d < input.length; d++) { 38 | i = (i + 1) % 256; 39 | keyIndex = (keyIndex + stateArray[i]) % 256; 40 | [stateArray[i], stateArray[keyIndex]] = [stateArray[keyIndex], stateArray[i]]; // Swap values 41 | output += String.fromCharCode(input.charCodeAt(d) ^ stateArray[(stateArray[i] + stateArray[keyIndex]) % 256]); 42 | } 43 | return output; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/util/other/EmsisoftUtil.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const EmsisoftUtil = function () { 4 | 5 | /** 6 | * Creates an array of domains from a hostname. 7 | * 8 | * @param {string} hostname - The hostname. 9 | * @returns {string[]} The array of domains. 10 | */ 11 | const createHostnameArray = function (hostname) { 12 | return hostname.split('.').map((_, i, arr) => arr.slice(i).join('.')); 13 | }; 14 | 15 | /** 16 | * Creates a string of hashes from an array of domains. 17 | * 18 | * @param {string[]} arr - The array of domains. 19 | * @returns {string} The string of hashes. 20 | */ 21 | const getStringOfHashes = function (arr) { 22 | return arr.map(createHash).join(','); 23 | }; 24 | 25 | /** 26 | * Creates a hash from a domain. 27 | * (Used by Emsisoft API) 28 | * 29 | * @param {string} domain - The domain to hash. 30 | * @returns {string} The hashed domain. 31 | */ 32 | const createHash = function (domain) { 33 | return MD5("Kd3fIjAq" + domain.toLowerCase(), null, false).toUpperCase(); 34 | }; 35 | 36 | /** 37 | * Finds the subdomain by hash. 38 | * 39 | * @param {string} hostname - The hostname to search. 40 | * @param {string} hash - The hash to find. 41 | * @returns {string} The subdomain or an empty string if not found. 42 | */ 43 | const findSubdomainByHash = function (hostname, hash) { 44 | return EmsisoftUtil.createHostnameArray(hostname).find(domain => EmsisoftUtil.createHash(domain) === hash) || ""; 45 | }; 46 | 47 | /** 48 | * Creates a new RegExp object. 49 | * 50 | * @param {string} value - The regex pattern 51 | * @param {boolean} [convertFromPCRE=false] - Whether to convert from PCRE 52 | * @param {string} [flags=''] - The regex flags 53 | * @returns {RegExp|null} The RegExp object or null if invalid 54 | */ 55 | const newRegExp = function (value, convertFromPCRE = false, flags = '') { 56 | try { 57 | // Handle PCRE conversion if required 58 | if (convertFromPCRE) { 59 | const match = /^\(\?([gmiu]+)\)/.exec(value); 60 | 61 | if (match) { 62 | match[1].split('').forEach(itm => { 63 | if (!flags.includes(itm)) { 64 | flags += itm; 65 | } 66 | }); 67 | 68 | value = value.replace(/^\(\?([gmiu]+)\)/, ''); 69 | } 70 | } 71 | 72 | // Attempt to create a RegExp with the provided flags 73 | return new RegExp(value, flags); 74 | } catch { 75 | try { 76 | // Retry by toggling the 'u' flag 77 | return new RegExp(value, flags.includes('u') ? flags.replace('u', '') : flags + 'u'); 78 | } catch { 79 | console.warn(`Invalid regex: "${value}"`); 80 | return null; 81 | } 82 | } 83 | }; 84 | 85 | return { 86 | createHostnameArray: createHostnameArray, 87 | getStringOfHashes: getStringOfHashes, 88 | createHash: createHash, 89 | findSubdomainByHash: findSubdomainByHash, 90 | newRegExp: newRegExp 91 | }; 92 | }(); 93 | -------------------------------------------------------------------------------- /src/main/util/other/SmartScreenUtil.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Utility module for SmartScreen hashing operations. 4 | const SmartScreenUtil = function () { 5 | const hashConstants = [ 6 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 7 | 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 8 | 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 9 | ]; 10 | 11 | const md5Constants = [ 12 | 3614090360, 3905402710, 606105819, 3250441966, 4118548399, 1200080426, 2821735955, 4249261313, 13 | 1770035416, 2336552879, 4294925233, 2304563134, 1804603682, 4254626195, 2792965006, 1236535329, 14 | 4129170786, 3225465664, 643717713, 3921069994, 3593408605, 38016083, 3634488961, 3889429448, 15 | 568446438, 3275163606, 4107603335, 1163531501, 2850285829, 4243563512, 1735328473, 2368359562, 16 | 4294588738, 2272392833, 1839030562, 4259657740, 2763975236, 1272893353, 4139469664, 3200236656, 17 | 681279174, 3936430074, 3572445317, 76029189, 3654602809, 3873151461, 530742520, 3299628645, 18 | 4096336452, 1126891415, 2878612391, 4237533241, 1700485571, 2399980690, 4293915773, 2240044497, 19 | 1873313359, 4264355552, 2734768916, 1309151649, 4149444226, 3174756917, 718787259, 3951481745 20 | ]; 21 | 22 | /** 23 | * Rotates the bits of a value to the left. 24 | * 25 | * @param {number} value - The value to rotate. 26 | * @param {number} shift - The number of bits to shift. 27 | * @returns {number} The rotated value. 28 | */ 29 | const rotateBits = function (value, shift) { 30 | return value << shift | value >>> 32 - shift 31 | }; 32 | 33 | /** 34 | * Computes the MD5 hash of the input string. 35 | * 36 | * @param input - The input string to hash. 37 | * @returns {[number,number,number,number]} - The MD5 hash as an array of four integers. 38 | */ 39 | const computeHash = function (input) { 40 | let hashValue; 41 | let intermediateValue; 42 | let paddedInput = input; 43 | let inputLength = 8 * paddedInput.length; 44 | 45 | // Append 1 bit and necessary padding 46 | paddedInput += String.fromCharCode(128); 47 | while ((paddedInput.length + 8) % 64) { 48 | paddedInput += String.fromCharCode(0); 49 | } 50 | 51 | // Append the length of the input 52 | for (let index = 0; index < 8; index++) { 53 | paddedInput += index < 4 ? String.fromCharCode(inputLength >>> 8 * index & 255) : String.fromCharCode(0); 54 | } 55 | 56 | let numberOfWords = paddedInput.length / 4; 57 | let getWord = function (index) { 58 | const wordIndex = 4 * index; 59 | return paddedInput.charCodeAt(wordIndex) 60 | | paddedInput.charCodeAt(wordIndex + 1) << 8 61 | | paddedInput.charCodeAt(wordIndex + 2) << 16 62 | | paddedInput.charCodeAt(wordIndex + 3) << 24; 63 | }; 64 | 65 | // Initialize hash variables 66 | let a = 1732584193; 67 | let b = 4023233417; 68 | let c = 2562383102; 69 | let d = 271733878; 70 | 71 | // Process each block of the padded input 72 | for (let blockIndex = 0; blockIndex < numberOfWords; blockIndex += 16) { 73 | let A = a; 74 | let B = b; 75 | let C = c; 76 | let D = d; 77 | 78 | for (let roundIndex = 0; roundIndex < 64; roundIndex++) { 79 | if (roundIndex < 16) { 80 | hashValue = B & C | ~B & D; 81 | intermediateValue = roundIndex; 82 | } else if (roundIndex < 32) { 83 | hashValue = D & B | ~D & C; 84 | intermediateValue = (5 * roundIndex + 1) % 16; 85 | } else if (roundIndex < 48) { 86 | hashValue = B ^ C ^ D; 87 | intermediateValue = (3 * roundIndex + 5) % 16; 88 | } else { 89 | hashValue = C ^ (B | ~D); 90 | intermediateValue = 7 * roundIndex % 16; 91 | } 92 | 93 | hashValue = hashValue + A + md5Constants[roundIndex] + getWord(blockIndex + intermediateValue); 94 | A = D; 95 | D = C; 96 | C = B; 97 | B += rotateBits(hashValue, hashConstants[roundIndex]); 98 | } 99 | 100 | a += A; 101 | b += B; 102 | c += C; 103 | d += D; 104 | } 105 | return [a, b, c, d] 106 | }; 107 | 108 | // Function to convert integer array to string 109 | const intArrayToString = function (array) { 110 | let resultString = ""; 111 | 112 | for (let index = 0, length = array.length; index < length; index++) { 113 | const value = array[index]; 114 | resultString += String.fromCharCode(value & 255); 115 | resultString += String.fromCharCode(value >>> 8 & 255); 116 | resultString += String.fromCharCode(value >>> 16 & 255); 117 | resultString += String.fromCharCode(value >>> 24 & 255); 118 | } 119 | return resultString 120 | }; 121 | 122 | // Function to reverse bits in an integer 123 | const reverseBits = function (value) { 124 | return (value >>> 16) + (value << 16) 125 | }; 126 | 127 | // Function to perform a hash operation 128 | const hashOperation = function (state, mult1, mult2, mult3, mult4, mult5) { 129 | state.t += state.buffer.getWord(state.index++); 130 | state.t = Math.imul(state.t, mult1) + Math.imul(reverseBits(state.t), mult2); 131 | state.t = Math.imul(reverseBits(state.t), mult3) + Math.imul(state.t, mult4); 132 | state.t += Math.imul(reverseBits(state.t), mult5); 133 | state.sum += state.t; 134 | }; 135 | 136 | // Function to perform a second type of hash operation 137 | const hashOperationExtended = function (state, mult1, mult2, mult3, mult4, mult5, mult6) { 138 | state.t += state.buffer.getWord(state.index++); 139 | state.t = Math.imul(state.t, mult1); 140 | state.u = reverseBits(state.t); 141 | state.t = Math.imul(state.u, mult2); 142 | state.t = Math.imul(reverseBits(state.t), mult3); 143 | state.t = Math.imul(reverseBits(state.t), mult4); 144 | state.t = Math.imul(reverseBits(state.t), mult5); 145 | state.t += Math.imul(state.u, mult6); 146 | state.sum += state.t; 147 | }; 148 | 149 | // Return the public API of the SmartScreenUtil module 150 | return { 151 | hash: function (input) { 152 | const hashOutput = computeHash(input); 153 | 154 | const outputData = { 155 | length: input.length / 4 & -2, getWord: function (index) { 156 | const wordIndex = 4 * index; 157 | return input.charCodeAt(wordIndex) 158 | | input.charCodeAt(wordIndex + 1) << 8 159 | | input.charCodeAt(wordIndex + 2) << 16 160 | | input.charCodeAt(wordIndex + 3) << 24; 161 | } 162 | }; 163 | 164 | const intermediateOutput = [0, 0]; 165 | const finalOutput = [0, 0]; 166 | 167 | // Process the first half of the hash calculation 168 | if (function (inputBuffer, hashArray, output) { 169 | let hashState = { 170 | buffer: inputBuffer, 171 | index: 0, 172 | sum: 0, 173 | t: 0 174 | }; 175 | 176 | let firstMultiplier = 1 | hashArray[0]; 177 | let secondMultiplier = 1 | hashArray[1]; 178 | 179 | // Ensure the input buffer is valid 180 | if (inputBuffer.length < 2 || 1 & inputBuffer.length) { 181 | return false; 182 | } 183 | 184 | // Process the buffer until all words are consumed 185 | while (hashState.buffer.length - hashState.index > 1) { 186 | hashOperation(hashState, firstMultiplier, 4010109435, 1755016095, 240755605, 3287280279); 187 | hashOperation(hashState, secondMultiplier, 3273069531, 3721207567, 984919853, 901586633); 188 | } 189 | 190 | output[0] = hashState.t; 191 | output[1] = hashState.sum; 192 | return true; 193 | }(outputData, hashOutput, finalOutput)) { 194 | const additionalOutput = [0, 0]; 195 | 196 | // Process the second half of the hash calculation 197 | (function (inputBuffer, hashArray, output) { 198 | let hashState = { 199 | buffer: inputBuffer, 200 | index: 0, 201 | sum: 0, 202 | t: 0, 203 | u: 0 204 | }; 205 | 206 | let firstMultiplier = 1 | hashArray[0]; 207 | let secondMultiplier = 1 | hashArray[1]; 208 | 209 | // Ensure the input buffer is valid 210 | if (inputBuffer.length < 2 || 1 & inputBuffer.length) { 211 | return false; 212 | } 213 | 214 | // Process the buffer until all words are consumed 215 | while (hashState.buffer.length - hashState.index > 1) { 216 | hashOperationExtended(hashState, firstMultiplier, 3482890513, 2265471903, 315537773, 629022083, 0); 217 | hashOperationExtended(hashState, secondMultiplier, 2725517045, 3548616447, 2090019721, 3215236969, 0); 218 | } 219 | 220 | output[0] = hashState.t; 221 | output[1] = hashState.sum; 222 | return true; 223 | })(outputData, hashOutput, additionalOutput) 224 | && (intermediateOutput[0] = finalOutput[0] ^ additionalOutput[0], 225 | intermediateOutput[1] = finalOutput[1] ^ additionalOutput[1]); 226 | } 227 | 228 | return { 229 | key: btoa(intArrayToString(hashOutput)), 230 | hash: btoa(intArrayToString(intermediateOutput)) 231 | } 232 | }, 233 | 234 | // Method to perform MD5 hashing 235 | md5: computeHash 236 | } 237 | }(); 238 | -------------------------------------------------------------------------------- /tests/protection-test-04-19-25.csv: -------------------------------------------------------------------------------- 1 | Provider Name,OpenPhish (485 links) %,AA419 (441 links) %,PhishStats (465 links) %,Malicious Sites (183 links) %,Grades (A-F) 2 | Microsoft,67,0,44,4,"D, F, F, F" 3 | Symantec,81,86,81,21,"B, B, B, F" 4 | Emsisoft,97,22,82,62,"A, F, B, D" 5 | Bitdefender,84,100,77,100,"B, A, C, A" 6 | Norton,92,77,82,90,"A, C, B, A" 7 | G DATA,100,78,100,100,"A, C, A, A" 8 | Cloudflare,65,0,53,26,"D, F, F, F" 9 | Quad9,55,22,38,25,"F, F, F, F" 10 | DNS0.eu,96,60,95,25,"A, D, A, F" 11 | CleanBrowsing,97,0,81,7,"A, F, B, F" 12 | CIRA,42,77,19,26,"F, C, F, F" 13 | AdGuard,44,4,11,8,"F, F, F, F" 14 | Switch.ch,96,3,96,7,"A, F, A, F" 15 | CERT-EE,51,2,76,26,"F, F, C, F" 16 | --------------------------------------------------------------------------------