[
  {
    "path": ".github/ISSUE_TEMPLATE/README.md",
    "content": "# Issue Template Management\n\n## New Issue Workflow (Discussion-First)\n\nTo improve issue triage and reduce noise from questions being filed as bugs, all issue creation now goes through GitHub Discussions first.\n\n### For Users:\n\n**❌ Direct issue creation is disabled**\n**✅ All reports must start as discussions**\n\nUsers will be redirected to:\n- 🐛 **Bug Reports** → [Q&A Discussions](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)\n- 💡 **Feature Requests** → [Ideas Discussions](https://github.com/projectdiscovery/katana/discussions/new?category=ideas)  \n- ❓ **Questions** → [Q&A Discussions](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)\n\n### For Maintainers:\n\n#### Converting Discussions to Issues:\n\n1. **Review the discussion** thoroughly\n2. **Determine if it's a valid bug/feature** (not just a question)\n3. **Convert to issue** using GitHub's \"Convert to Issue\" feature:\n   - Go to the discussion\n   - Click \"⋯\" menu → \"Convert to issue\"\n   - Add appropriate labels and assignees\n   \n#### Triage Guidelines:\n\n**Convert to Issue:**\n- ✅ Confirmed bugs with reproduction steps\n- ✅ Well-defined feature requests with clear use cases\n- ✅ Security vulnerabilities (after initial assessment)\n\n**Keep as Discussion:**\n- ❌ Usage questions (\"How do I...?\")\n- ❌ Configuration help\n- ❌ Unclear or incomplete bug reports\n- ❌ Feature ideas that need more discussion/refinement\n\n### Benefits:\n\n- 📊 **Better issue quality** - Only confirmed bugs/features become issues\n- 🎯 **Easier triage** - Questions don't clutter the issue tracker\n- 💬 **Community involvement** - Discussion encourages collaboration before formal issues\n- 🧹 **Cleaner issue tracker** - Focus on actionable items only\n\n## Re-enabling Templates (If Needed)\n\nIf you need to temporarily re-enable direct issue creation:\n\n```bash\n# Re-enable templates\nmv issue-report.md.disabled issue-report.md\nmv feature_request.md.disabled feature_request.md\n\n# Update config.yml to add them back\n```\n\n## Template Files:\n\n- `config.yml` - Main configuration (redirects all to discussions)\n- `issue-report.md.disabled` - Bug report template (disabled)\n- `feature_request.md.disabled` - Feature request template (disabled)\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n\ncontact_links:\n  - name: Report a Bug (Discussion First)\n    url: https://github.com/projectdiscovery/katana/discussions/new?category=q-a\n    about: Report bugs or issues via discussion for proper triage before issue creation\n\n  - name: Feature Request (Discussion First)\n    url: https://github.com/projectdiscovery/katana/discussions/new?category=ideas\n    about: Share feature ideas via discussion for evaluation before implementation\n\n  - name: Ask a Question\n    url: https://github.com/projectdiscovery/katana/discussions/new?category=q-a\n    about: Ask questions about usage, configuration, or troubleshooting\n\n  - name: General Discussion\n    url: https://github.com/projectdiscovery/katana/discussions/new?category=general\n    about: General discussion about the project\n\n  - name: Security Issues\n    url: mailto:security@projectdiscovery.io\n    about: Report security vulnerabilities privately via email\n\n  - name: Discord Community\n    url: https://discord.gg/projectdiscovery\n    about: Connect with PD Team and community for real-time discussion"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md.disabled",
    "content": "---\nname: Feature request\nabout: Request feature to implement in this project\nlabels: 'Type: Enhancement'\n---\n\n<!--\n1. Please make sure to provide a detailed description with all the relevant information that might be required to start working on this feature.\n2. In case you are not sure about your request or whether the particular feature is already supported or not, please start a discussion instead.\n3. GitHub Discussion: https://github.com/projectdiscovery/katana/discussions/categories/ideas\n4. Join our discord server at https://discord.gg/projectdiscovery to discuss the idea on the #katana channel.\n-->\n\n### Please describe your feature request:\n<!-- A clear and concise description of feature to implement -->\n\n### Describe the use case of this feature:\n<!-- A clear and concise description of the feature request's motivation and the use-cases in which it could be useful. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/issue-report.md.disabled",
    "content": "---\nname: Issue report\nabout: Create a report to help us to improve the project\nlabels: 'Type: Bug'\n\n---\n\n<!-- \n1. Please search to see if an issue already exists for the bug you encountered.\n2. For support requests, FAQs or \"How to\" questions, please use the GitHub Discussions section instead - https://github.com/projectdiscovery/katana/discussions or\n3. Join our discord server at https://discord.gg/projectdiscovery and post the question on the #katana channel.\n-->\n\n<!-- ISSUES MISSING IMPORTANT INFORMATION MAY BE CLOSED WITHOUT INVESTIGATION. -->\n\n### katana version:\n<!-- You can find current version of katana with \"katana -version\" -->\n<!-- We only accept issues that are reproducible on the latest version of katana. -->\n<!-- You can find the latest version of project at https://github.com/projectdiscovery/katana/releases/ -->\n\n### Current Behavior:\n<!-- A concise description of what you're experiencing. -->\n\n### Expected Behavior:\n<!-- A concise description of what you expected to happen. -->\n\n### Steps To Reproduce:\n<!--\nExample: steps to reproduce the behavior:\n1. Run 'katana ..'\n2. See error...\n-->\n\n\n### Anything else:\n<!-- Links? References? Screnshots? Anything that will give us more context about the issue that you are encountering! -->"
  },
  {
    "path": ".github/MAINTAINER_GUIDE.md",
    "content": "# Maintainer Guide: Discussion-First Issue Management\n\n## Overview\n\nKatana now uses a **discussion-first approach** for issue management to improve triage quality and reduce noise from questions being filed as bugs.\n\n## How It Works\n\n### 1. **All Reports Start as Discussions**\n- Users cannot create issues directly\n- All bug reports → **Q&A Discussions**  \n- All feature requests → **Ideas Discussions**\n- All questions → **Q&A Discussions**\n\n### 2. **Automated Triage Helper**\n- Auto-responds to discussions with helpful guidance\n- Auto-flags potential bugs with keywords detection\n- Provides checklists for proper bug reporting\n\n### 3. **Maintainer Conversion Process**\n- Review discussions for completeness\n- Convert valid issues using GitHub's built-in feature\n- Apply appropriate labels during conversion\n\n## Conversion Guidelines\n\n### 🐛 **Bug Reports** → Convert to Issue When:\n\n**Well-Defined Problems:**\n- Clear reproduction steps provided\n- Katana version specified\n- Expected vs actual behavior described  \n- Environment details included\n- Error messages/logs included\n\n**Confirmed Bugs:**\n- Issue reproduced by maintainer or community\n- Not a configuration/usage question\n- Not working as designed\n\n**Keep as Discussion:**\n- Incomplete information\n- Usage questions (\"How do I...?\")\n- Configuration problems\n- Working as intended\n\n### 💡 **Feature Requests** → Convert to Issue When:\n\n**Solid Proposals:**\n- Clear use case defined\n- Benefits to community explained\n- Implementation approach considered\n- Not easily achievable with existing features\n\n**Community Support:**\n- Multiple users expressing interest\n- Maintainer approval for implementation\n- Fits project roadmap\n\n**Keep as Discussion:**\n- Vague ideas needing refinement\n- Better suited as external tools/plugins\n- Conflicts with project goals\n- Needs more community input\n\n## Conversion Process\n\n### Using GitHub's Convert Feature:\n\n1. **Open the discussion**\n2. **Click the \"⋯\" menu** (top right)\n3. **Select \"Convert to issue\"**\n4. **Choose repository** (same repo)\n5. **Review title/body** - edit if needed\n6. **Add labels:**\n   - `Type: Bug` for confirmed bugs\n   - `Type: Enhancement` for approved features  \n   - `Priority: High/Medium/Low` as appropriate\n   - `Component: Engine/Parser/Output` etc.\n\n### Template for Converted Issues:\n\nWhen converting, consider adding this note:\n\n```markdown\n**Converted from Discussion:** #[discussion_number]\n\n<!-- Original discussion provided community input and initial triage -->\n\n[Original discussion content here]\n\n---\n\n**Maintainer Notes:**\n- [ ] Issue confirmed through discussion\n- [ ] Reproduction steps verified  \n- [ ] Ready for implementation/investigation\n```\n\n## Workflow Benefits\n\n### **For Project Health:**\n- **Cleaner issue tracker** - Only actionable items\n- **Better metrics** - Issues vs discussions clearly separated\n- **Faster resolution** - Less time sorting questions from bugs\n\n### **For Community:**\n- **Inclusive discussions** - Everyone can participate in triage\n- **Better help** - Community can answer questions quickly  \n- **Learning opportunity** - Users see resolution process\n\n### **For Maintainers:**\n- **Pre-filtered issues** - Only valid bugs/features reach issue tracker\n- **Rich context** - Discussion history provides background\n- **Community input** - Others help validate before conversion\n\n## Examples\n\n### **Good Bug Discussion → Issue Conversion**\n\n**Discussion Title:** \"Katana crashes when using -hl with custom headers\"\n\n**Discussion Body:** \n- Katana version: v1.2.1\n- Command: `katana -u example.com -hl -H \"Custom: value\"`\n- Error: panic in hybrid engine\n- Platform: macOS 14.1\n- Reproduction: consistent crash\n\n**→ Convert to Issue:** Clear bug with reproduction steps\n\n### **Question → Keep as Discussion**\n\n**Discussion Title:** \"How to crawl only PDF files?\"\n\n**Discussion Body:**\n- New user question\n- Asking for usage help\n- Not a bug or feature request\n\n**→ Keep as Discussion:** Usage question, answer in discussion\n\n### **Needs More Info → Keep as Discussion**\n\n**Discussion Title:** \"Katana doesn't work\"\n\n**Discussion Body:**\n- Vague description\n- No version, command, or error details\n- No reproduction steps\n\n**→ Keep as Discussion:** Request more information first\n\n## Quick Reference\n\n| Type | Action | Labels for Conversion |\n|------|---------|-------------------|\n| **Confirmed Bug** | Convert → Issue | `Type: Bug`, `Priority: [level]` |\n| **Approved Feature** | Convert → Issue | `Type: Enhancement`, `Priority: [level]` |  \n| **Usage Question** | Keep → Discussion | N/A |\n| **Needs Info** | Keep → Discussion | N/A |\n| **Security Issue** | Email → security@projectdiscovery.io | N/A |\n\nThis workflow ensures high-quality issues while maintaining an inclusive, helpful community environment!\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## Proposed changes\n\n<!-- Describe the overall picture of your modifications to help maintainers understand the pull request. PRs are required to be associated to their related issue tickets or feature request. -->\n\n### Proof\n\n<!-- How has this been tested? Please describe the tests that you ran to verify your changes. -->\n\n## Checklist\n\n<!-- Put an \"x\" in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. -->\n\n- [ ] Pull request is created against the [dev](https://github.com/projectdiscovery/katana/tree/dev) branch\n- [ ] All checks passed (lint, unit/integration/regression tests etc.) with my changes\n- [ ] I have added tests that prove my fix is effective or that my feature works\n- [ ] I have added necessary documentation (if appropriate)"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n\n  # Maintain dependencies for go modules\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    target-branch: \"dev\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    labels:\n      - \"Type: Maintenance\"\n    allow:\n      - dependency-name: \"github.com/projectdiscovery/*\"\n\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    target-branch: \"dev\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    labels:\n      - \"Type: Maintenance\"\n\n  # Maintain dependencies for docker\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    target-branch: \"dev\"\n    commit-message:\n      prefix: \"chore\"\n      include: \"scope\"\n    labels:\n      - \"Type: Maintenance\""
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    authors:\n      - app/dependabot\n      - dependabot\n  categories:\n    - title: 🎉 New Features\n      labels:\n        - \"Type: Enhancement\"\n    - title: 🐞 Bug Fixes\n      labels:\n        - \"Type: Bug\" \n    - title: 🔨 Maintenance\n      labels:\n        - \"Type: Maintenance\" \n    - title: Other Changes\n      labels:\n        - \"*\""
  },
  {
    "path": ".github/workflows/build-test.yml",
    "content": "name: 🔨 Build Test\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '**.go'\n      - '**.mod'\njobs:\n  lint:\n    name: \"Lint\"\n    if: \"${{ !endsWith(github.actor, '[bot]') }}\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go@v1\n      - uses: projectdiscovery/actions/golangci-lint/v2@v1\n\n  build:\n    name: Test Builds\n    needs: [lint]\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v6\n\n      - name: Set up Go\n        uses: projectdiscovery/actions/setup/go@v1\n\n      - name: Test\n        run: go test ./...\n        working-directory: .\n\n      - name: Build\n        run: go build .\n        working-directory: cmd/katana/\n\n      - name: Integration Tests\n        env:\n          GH_ACTION: true\n        run: bash run.sh\n        working-directory: integration_tests/\n\n      - name: Install\n        run: go install\n        working-directory: cmd/katana/\n\n      - name: Race Condition Tests\n        run: go build -race .\n        working-directory: cmd/katana/\n\n      \n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: 🚨 CodeQL Analysis\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches:\n      - dev\n    paths:\n      - '**.go'\n      - '**.mod'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'go' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v6\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: ${{ matrix.language }}\n\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v4\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4"
  },
  {
    "path": ".github/workflows/compat-checks.yaml",
    "content": "name: ♾️ Compatibility Checks\n\non:\n  pull_request:\n    types: [opened, synchronize]\n    branches:\n      - dev\n\njobs:\n  check:\n    if: github.actor == 'dependabot[bot]'\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: actions/checkout@v6\n      - uses: projectdiscovery/actions/setup/go/compat-checks@v1\n"
  },
  {
    "path": ".github/workflows/dep-auto-merge.yml",
    "content": "name: 🤖 Auto Merge\n\non:\n  pull_request_review:\n    types: [submitted]\n  workflow_run:\n    workflows: [\"♾️ Compatibility Check\"]\n    types:\n      - completed\n\npermissions:\n  pull-requests: write\n  issues: write\n  repository-projects: write\n\njobs:\n  auto-merge:\n    runs-on: ubuntu-latest\n    if: github.actor == 'dependabot[bot]'\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          token: ${{ secrets.DEPENDABOT_PAT }}\n\n      - uses: ahmadnassri/action-dependabot-auto-merge@v2\n        with:\n          github-token: ${{ secrets.DEPENDABOT_PAT }}\n          target: all"
  },
  {
    "path": ".github/workflows/discussion-triage.yml",
    "content": "name: Discussion Triage Helper\n\non:\n  discussion:\n    types: [created]\n\npermissions:\n  discussions: write\n\njobs:\n  triage:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Add initial response to discussions\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const { discussion } = context.payload;\n            \n            // Add helpful response based on discussion category\n            let response = \"\";\n            \n            if (discussion.category.slug === \"q-a\") {\n              if (discussion.title.toLowerCase().includes(\"bug\") || \n                  discussion.title.toLowerCase().includes(\"error\") || \n                  discussion.title.toLowerCase().includes(\"crash\") ||\n                  discussion.title.toLowerCase().includes(\"fail\")) {\n                \n                response = \"Thanks for the discussion! This looks like it might be a bug report.\\n\\n\" +\n                          \"**For Maintainers:** If this is confirmed to be a bug with proper reproduction steps, consider converting to an issue using \\\"Convert to issue\\\" in the discussion menu.\\n\\n\" +\n                          \"**For the Reporter:** To help us triage this effectively, please provide:\\n\" +\n                          \"- [ ] Katana version (`katana -version`)\\n\" +\n                          \"- [ ] Complete command that reproduces the issue\\n\" +\n                          \"- [ ] Expected vs actual behavior\\n\" +\n                          \"- [ ] Operating system and environment details\\n\" +\n                          \"- [ ] Any error messages or logs\\n\\n\" +\n                          \"This helps us determine if this should become a tracked issue. Thank you!\";\n              \n              } else {\n                response = \"Thanks for your question!\\n\\n\" +\n                          \"The community and maintainers will help answer this. If this turns out to be a bug or feature request after discussion, a maintainer can convert it to a tracked issue.\\n\\n\" +\n                          \"**Tip:** For faster responses, you can also join our [Discord server](https://discord.gg/projectdiscovery) for real-time help!\";\n              }\n            }\n            \n            else if (discussion.category.slug === \"ideas\") {\n              response = \"Thanks for sharing this idea!\\n\\n\" +\n                        \"**For Maintainers:** If this feature request is well-defined and approved for implementation, consider converting to an issue using \\\"Convert to issue\\\" in the discussion menu.\\n\\n\" +\n                        \"**For the Reporter:** To help evaluate this feature:\\n\" +\n                        \"- [ ] Describe the specific use case this would solve\\n\" +\n                        \"- [ ] Explain how this would benefit the katana user community\\n\" +  \n                        \"- [ ] Consider if this could be implemented as a plugin or external tool\\n\" +\n                        \"- [ ] Check if similar functionality already exists\\n\\n\" +\n                        \"Well-defined features with clear benefits may be converted to tracked issues for implementation. Thanks for contributing!\";\n            }\n            \n            if (response) {\n              await github.rest.discussions.createComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                discussion_number: discussion.number,\n                body: response\n              });\n            }\n\n      - name: Auto-label potential bug reports\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const { discussion } = context.payload;\n            const title = discussion.title.toLowerCase();\n            const body = discussion.body.toLowerCase();\n            \n            // Check for keywords that suggest this might be a bug\n            const bugKeywords = [\n              \"bug\", \"error\", \"crash\", \"fail\", \"broken\", \"issue\", \"problem\", \n              \"exception\", \"panic\", \"segfault\", \"hang\", \"freeze\", \"incorrect\"\n            ];\n            \n            const hasBugKeywords = bugKeywords.some(keyword => \n              title.includes(keyword) || body.includes(keyword)\n            );\n            \n            if (hasBugKeywords && discussion.category.slug === \"q-a\") {\n              // Add a comment suggesting this might need to be converted to an issue\n              const detectedKeywords = bugKeywords.filter(k => title.includes(k) || body.includes(k));\n              await github.rest.discussions.createComment({\n                owner: context.repo.owner,\n                repo: context.repo.repo,  \n                discussion_number: discussion.number,\n                body: \"**Maintainer Note:** This discussion contains potential bug keywords and may need to be converted to a tracked issue after confirmation.\\n\\n\" +\n                      \"**Keywords detected:** \" + detectedKeywords.join(', ')\n              });\n            }\n"
  },
  {
    "path": ".github/workflows/dockerhub-push.yml",
    "content": "name: 🌥 Docker Push\n\non:\n  workflow_run:\n    workflows: [\"🎉 Release Binary\"]\n    types:\n      - completed\n  workflow_dispatch:\n\njobs:\n  docker:\n    runs-on: ubuntu-latest-16-cores\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Get Github tag\n        id: meta\n        run: |\n          curl --silent \"https://api.github.com/repos/projectdiscovery/katana/releases/latest\" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Login to DockerHub\n        uses: docker/login-action@v3 \n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_TOKEN }}\n\n      - name: Build and push\n        uses: docker/build-push-action@v6\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm64\n          push: true\n          tags: projectdiscovery/katana:latest,projectdiscovery/katana:${{ steps.meta.outputs.TAG }}"
  },
  {
    "path": ".github/workflows/functional-test.yml",
    "content": "name: 🧪 Functional Test\n\non:\n  pull_request:\n    paths:\n      - '**.go'\n      - '**.mod'\n  workflow_dispatch:\n\njobs:  \n  functional:\n    name: Functional Test\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, windows-latest, macOS-latest]\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v6\n\n      - name: Set up Go\n        uses: projectdiscovery/actions/setup/go@v1\n\n      - name: Functional Tests\n        run: |\n          chmod +x run.sh\n          bash run.sh ${{ matrix.os }}\n        working-directory: cmd/functional-test\n"
  },
  {
    "path": ".github/workflows/release-binary.yml",
    "content": "name: 🎉 Release Binary\n\non:\n  push:\n    tags:\n      - v*\n  workflow_dispatch:\n\njobs:\n  build-mac:\n    runs-on: macos-latest\n    steps:\n      - name: \"Check out code\"\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: \"Set up Go\"\n        uses: projectdiscovery/actions/setup/go@v1\n\n      - name: \"Create release on GitHub\"\n        uses: goreleaser/goreleaser-action@v7\n        with:\n          args: \"release -f .goreleaser/mac.yml --clean\"\n          version: latest\n          workdir: .\n        env:\n          GITHUB_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n\n  build-windows:\n    runs-on: windows-latest-8-cores\n    steps:\n      - name: \"Check out code\"\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: \"Set up Go\"\n        uses: projectdiscovery/actions/setup/go@v1\n\n      - name: \"Create release on GitHub\"\n        uses: goreleaser/goreleaser-action@v7\n        with:\n          args: \"release -f .goreleaser/windows.yml --clean\"\n          version: latest\n          workdir: .\n        env:\n          GITHUB_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n\n  build-linux:\n    runs-on: ubuntu-latest-16-cores\n    steps:\n      - name: \"Check out code\"\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: \"Set up Go\"\n        uses: projectdiscovery/actions/setup/go@v1\n\n      # todo: musl compatible?\n      - name: Install Dependences\n        run: sudo apt install gcc-aarch64-linux-gnu\n\n      - name: \"Create release on GitHub\"\n        uses: goreleaser/goreleaser-action@v7\n        with:\n          args: \"release -f .goreleaser/linux.yml --clean\"\n          version: latest\n          workdir: .\n        env:\n          GITHUB_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n          SLACK_WEBHOOK: \"${{ secrets.RELEASE_SLACK_WEBHOOK }}\"\n          DISCORD_WEBHOOK_ID: \"${{ secrets.DISCORD_WEBHOOK_ID }}\"\n          DISCORD_WEBHOOK_TOKEN: \"${{ secrets.DISCORD_WEBHOOK_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/release-test.yml",
    "content": "name: 🔨 Release Test\n\non:\n  pull_request:\n    paths:\n      - \"**.yml\"\n      - \"**.go\"\n      - \"**.mod\"\n  workflow_dispatch:\n\njobs:\n  release-test-mac:\n    runs-on: macos-latest\n    steps:\n      - name: \"Check out code\"\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Set up Go\n        uses: projectdiscovery/actions/setup/go@v1\n\n      - name: release test\n        uses: projectdiscovery/actions/goreleaser@v1\n        with:\n          args: --config=.goreleaser/mac.yml\n          workdir: .\n        env:\n          GITHUB_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n\n  release-test-linux:\n    runs-on: ubuntu-latest-16-cores\n    steps:\n      - name: \"Check out code\"\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Set up Go\n        uses: projectdiscovery/actions/setup/go@v1\n\n      # todo: musl compatible?\n      - name: Install Dependences\n        run: sudo apt update && sudo apt install gcc-aarch64-linux-gnu\n\n      - name: release test\n        uses: projectdiscovery/actions/goreleaser@v1\n        with:\n          args: --config=.goreleaser/linux.yml\n          workdir: .\n        env:\n          GITHUB_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\"\n\n  release-test-windows:\n    runs-on: windows-latest-8-cores\n    steps:\n      - name: \"Check out code\"\n        uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - name: Set up Go\n        uses: projectdiscovery/actions/setup/go@v1\n\n      - name: release test\n        uses: projectdiscovery/actions/goreleaser@v1\n        with:\n          args: --config=.goreleaser/windows.yml\n          workdir: .\n        env:\n          GITHUB_TOKEN: \"${{ secrets.GITHUB_TOKEN }}\""
  },
  {
    "path": ".github/workflows/security-crawl-maze-score.yaml",
    "content": "name: 🙏🏻 Security Crawl Maze Score\n\non:\n  workflow_dispatch:\n\njobs:\n  build:\n    name: Run Scoring\n    runs-on: ubuntu-latest-16-cores\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v6\n\n      - name: Set up Go\n        uses: projectdiscovery/actions/setup/go@v1\n\n      - name: Build\n        run: go build .\n        working-directory: cmd/katana/\n\n      - name: Run Katana Standard\n        run: ./katana -u https://security-crawl-maze.app/ -kf all -jc -jsluice -d 10 -o output_standard.txt -cos node_modules\n        working-directory: cmd/katana\n        \n      - name: Run Katana Headless\n        run: ./katana -u https://security-crawl-maze.app/ -kf all -jc -jsluice -d 10 -headless -o output_headless.txt -cos node_modules\n        working-directory: cmd/katana\n\n      - name: Run Score\n        run: go build .; ./crawl-maze-score ../../katana/output_standard.txt ../../katana/output_headless.txt\n        working-directory: cmd/tools/crawl-maze-score"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: 💤 Stale\n\non:\n  schedule:\n    - cron: '0 0 * * 0' # Weekly\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    permissions:\n      actions: write\n      contents: write # only for delete-branch option\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: actions/stale@v10\n        with:\n          days-before-stale: 90\n          days-before-close: 7\n          stale-issue-label: \"Status: Stale\"\n          stale-pr-label: \"Status: Stale\"\n          stale-issue-message: >\n            This issue has been automatically marked as stale because it has not\n            had recent activity. It will be closed in 7 days if no further\n            activity occurs. Thank you for your contributions!\n          stale-pr-message: >\n            This pull request has been automatically marked as stale due to\n            inactivity. It will be closed in 7 days if no further activity\n            occurs. Please update if you wish to keep it open.\n          close-issue-message: >\n            This issue has been automatically closed due to inactivity. If you\n            think this is a mistake or would like to continue the discussion,\n            please comment or feel free to reopen it.\n          close-pr-message: >\n            This pull request has been automatically closed due to inactivity.\n            If you think this is a mistake or would like to continue working on\n            it, please comment or feel free to reopen it.\n          close-issue-label: \"Status: Abandoned\"\n          close-pr-label: \"Status: Abandoned\"\n          exempt-issue-labels: \"Status: Abandoned\"\n          exempt-pr-labels: \"Status: Abandoned\""
  },
  {
    "path": ".github/workflows/workflow-monitor.yml",
    "content": "name: Workflow Monitor\n\non:\n  schedule:\n    # Run weekly on Mondays at 9 AM UTC\n    - cron: '0 9 * * 1'\n  workflow_dispatch: # Allow manual triggering\n\npermissions:\n  issues: read\n  discussions: read\n\njobs:\n  monitor-workflow:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check Discussion vs Issue Activity\n        uses: actions/github-script@v8\n        with:\n          script: |\n            const oneWeekAgo = new Date();\n            oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);\n            const since = oneWeekAgo.toISOString();\n            \n            // Get discussions created in the last week\n            const discussions = await github.graphql(`\n              query($owner: String!, $repo: String!, $since: DateTime!) {\n                repository(owner: $owner, name: $repo) {\n                  discussions(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n                    nodes {\n                      title\n                      createdAt\n                      category {\n                        name\n                        slug\n                      }\n                      comments(first: 1) {\n                        totalCount\n                      }\n                    }\n                  }\n                }\n              }\n            `, {\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              since: since\n            });\n            \n            // Get issues created in the last week\n            const issues = await github.rest.issues.listForRepo({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              since: since,\n              state: 'all'\n            });\n            \n            // Filter discussions created in the last week\n            const recentDiscussions = discussions.repository.discussions.nodes.filter(\n              d => new Date(d.createdAt) >= oneWeekAgo\n            );\n            \n            // Generate and log simple report\n            console.log(\"=== Discussion-First Workflow Report ===\");\n            console.log(`Period: ${oneWeekAgo.toDateString()} - ${new Date().toDateString()}`);\n            console.log(`Discussions Created: ${recentDiscussions.length}`);\n            console.log(`Issues Created: ${issues.data.length}`);\n            console.log(`Conversion Ratio: ${issues.data.length}/${recentDiscussions.length}`);\n            \n            if (recentDiscussions.length > 0) {\n              console.log(\"\\nDiscussion Categories:\");\n              recentDiscussions.forEach(d => {\n                console.log(`- ${d.category.name}: ${d.title} (${d.comments.totalCount} comments)`);\n              });\n            } else {\n              console.log(\"No discussions this week\");\n            }\n            \n            if (issues.data.length > 0) {\n              console.log(\"\\nDirect Issues Created:\");\n              issues.data.forEach(i => {\n                console.log(`- ${i.title} (${new Date(i.created_at).toDateString()})`);\n              });\n              console.log(\"\\nWARNING: Direct issues were created - check workflow configuration\");\n            } else {\n              console.log(\"\\nNo direct issues created - workflow working correctly\");\n            }\n            \n            const responseRate = recentDiscussions.length > 0 ? \n              (recentDiscussions.filter(d => d.comments.totalCount > 0).length / recentDiscussions.length * 100).toFixed(1) : 0;\n            console.log(`Community Response Rate: ${responseRate}%`);\n            \n            // You could also create an issue or discussion with this report\n            // For now, just log it to Actions output\n"
  },
  {
    "path": ".gitignore",
    "content": "security-crawl-maze/\ncmd/katana/katana\nkatana\n*.exe\nkatana_*/\nkatana_*/\ndist/\n\n.vscode\n.devcontainer\n.DS_Store\n.idea\n"
  },
  {
    "path": ".goreleaser/linux.yml",
    "content": "version: 2\n\nenv:\n  - GO111MODULE=on\nbefore:\n  hooks:\n    - go mod tidy\nproject_name: katana\nbuilds:\n  - id: katana-linux-amd64-generic\n    ldflags:\n      - -s -w\n    binary: katana\n    env:\n      - CGO_ENABLED=1\n    main: ./cmd/katana/main.go\n    goos:\n      - linux\n    goarch:\n      - amd64\n\n  - id: katana-linux-i386-generic\n    ldflags:\n      - -s -w\n    binary: katana\n    main: ./cmd/katana/main.go\n    goos:\n      - linux\n    goarch:\n      - 386\n\n  - id: katana-linux-arm\n    ldflags:\n      - -s -w\n    binary: katana\n    env:\n      - CGO_ENABLED=1\n      - CC=aarch64-linux-gnu-gcc\n    main: ./cmd/katana/main.go\n    goos:\n      - linux\n    goarch:\n      - arm64\n      \narchives:\n- formats: zip\n\nchecksum:\n  name_template: \"{{ .ProjectName }}-linux-checksums.txt\"\n\nannounce:\n  slack:\n    enabled: true\n    channel: '#release'\n    username: GoReleaser\n    message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}'\n\n  discord:\n    enabled: true\n    message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}'"
  },
  {
    "path": ".goreleaser/mac.yml",
    "content": "version: 2\n\nenv:\n  - GO111MODULE=on\nbefore:\n  hooks:\n    - go mod tidy\nproject_name: katana\nbuilds:\n  - id: katana-darwin\n    ldflags:\n      - -s -w\n    binary: katana\n    env:\n      - CGO_ENABLED=1\n    main: ./cmd/katana/main.go\n    goos:\n      - darwin\n    goarch:\n      - amd64\n      - arm64\n      - 386\n      - arm\n\narchives:\n  - formats: zip\n    name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os \"darwin\" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}'\n\nchecksum:\n  name_template: \"{{ .ProjectName }}-mac-checksums.txt\"\n"
  },
  {
    "path": ".goreleaser/windows.yml",
    "content": "version: 2\n\nenv:\n  - GO111MODULE=on\nbefore:\n  hooks:\n    - go mod tidy\nproject_name: katana\nbuilds:\n  - id: katana-windows\n    ldflags:\n      - -s -w\n    binary: katana\n    env:\n    - CGO_ENABLED=1\n    main: ./cmd/katana/main.go\n    goos:\n      - windows\n    goarch:\n      - 386\n      - arm64\n      - amd64\n\narchives:\n- formats: zip\n\nchecksum:\n  name_template: \"{{ .ProjectName }}-windows-checksums.txt\"\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:1.25.7-alpine AS build-env\nRUN apk add --no-cache git gcc musl-dev\nWORKDIR /app\nCOPY . /app\nRUN go mod download\nRUN go build ./cmd/katana\n\nFROM alpine:3.23.2\nRUN apk add --no-cache bind-tools ca-certificates chromium\nCOPY --from=build-env /app/katana /usr/local/bin/\n\nENTRYPOINT [\"katana\"]\n"
  },
  {
    "path": "LICENSE.md",
    "content": "MIT License\n\nCopyright (c) 2022 ProjectDiscovery\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# Go parameters\nGOCMD=go\nGOBUILD=$(GOCMD) build\nGOMOD=$(GOCMD) mod\nGOTEST=$(GOCMD) test\nGOFLAGS := -v\n# This should be disabled if the binary uses pprof\nLDFLAGS := -s -w\n\nifneq ($(shell go env GOOS),darwin)\nLDFLAGS := -extldflags \"-static\"\nendif\n\nall: build\nbuild:\n\t$(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o \"katana\" cmd/katana/main.go\ntest:\n\t$(GOTEST) $(GOFLAGS) ./...\nintegration:\n\tcd integration_tests; bash run.sh cd ..\ntidy:\n\t$(GOMOD) tidy\nlint:\n\tgolangci-lint run ./...\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\n  <img src=\"https://user-images.githubusercontent.com/8293321/196779266-421c79d4-643a-4f73-9b54-3da379bbac09.png\" alt=\"katana\" width=\"200px\">\n  <br>\n</h1>\n\n<h4 align=\"center\">A next-generation crawling and spidering framework</h4>\n\n<p align=\"center\">\n<a href=\"https://goreportcard.com/report/github.com/projectdiscovery/katana\"><img src=\"https://goreportcard.com/badge/github.com/projectdiscovery/katana\"></a>\n<a href=\"https://github.com/projectdiscovery/katana/issues\"><img src=\"https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat\"></a>\n<a href=\"https://github.com/projectdiscovery/katana/releases\"><img src=\"https://img.shields.io/github/release/projectdiscovery/katana\"></a>\n<a href=\"https://twitter.com/pdiscoveryio\"><img src=\"https://img.shields.io/twitter/follow/pdiscoveryio.svg?logo=twitter\"></a>\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://img.shields.io/discord/695645237418131507.svg?logo=discord\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"#features\">Features</a> •\n  <a href=\"#installation\">Installation</a> •\n  <a href=\"#usage\">Usage</a> •\n  <a href=\"#scope-control\">Scope</a> •\n  <a href=\"#crawler-configuration\">Config</a> •\n  <a href=\"#filters\">Filters</a> •\n  <a href=\"https://discord.gg/projectdiscovery\">Join Discord</a>\n</p>\n\n\n# Features\n\n![image](https://user-images.githubusercontent.com/8293321/199371558-daba03b6-bf9c-4883-8506-76497c6c3a44.png)\n\n - Fast And fully configurable web crawling\n - **Standard** and **Headless** mode\n - **JavaScript** parsing / crawling\n - Customizable **automatic form filling**\n - **Scope control** - Preconfigured field / Regex \n - **Customizable output** - Preconfigured fields\n - INPUT - **STDIN**, **URL** and **LIST**\n - OUTPUT - **STDOUT**, **FILE** and **JSON**\n\n\n## Installation\n\nkatana requires Go 1.25+ to install successfully. If you encounter any installation issues, we recommend trying with the latest available version of Go, as the minimum required version may have changed. Run the command below or download a pre-compiled binary from the [release page](https://github.com/projectdiscovery/katana/releases).\n\n```console\nCGO_ENABLED=1 go install github.com/projectdiscovery/katana/cmd/katana@latest\n```\n\n**More options to install / run katana-**\n\n<details>\n  <summary>Docker</summary>\n\n> To install / update docker to latest tag -\n\n```sh\ndocker pull projectdiscovery/katana:latest\n```\n\n> To run katana in standard mode using docker -\n\n\n```sh\ndocker run projectdiscovery/katana:latest -u https://tesla.com\n```\n\n> To run katana in headless mode using docker -\n\n```sh\ndocker run projectdiscovery/katana:latest -u https://tesla.com -system-chrome -headless\n```\n\n</details>\n\n<details>\n  <summary>Ubuntu</summary>\n\n> It's recommended to install the following prerequisites -\n\n```sh\nsudo apt update\nsudo snap refresh\nsudo apt install zip curl wget git\nsudo snap install golang --classic\nwget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - \nsudo sh -c 'echo \"deb http://dl.google.com/linux/chrome/deb/ stable main\" >> /etc/apt/sources.list.d/google.list'\nsudo apt update \nsudo apt install google-chrome-stable\n```\n\n> install katana -\n\n\n```sh\ngo install github.com/projectdiscovery/katana/cmd/katana@latest\n```\n\n</details>\n\n## Usage\n\n```console\nkatana -h\n```\n\nThis will display help for the tool. Here are all the switches it supports.\n\n```console\nKatana is a fast crawler focused on execution in automation\npipelines offering both headless and non-headless crawling.\n\nUsage:\n  ./katana [flags]\n\nFlags:\nINPUT:\n   -u, -list string[]     target url / list to crawl\n   -resume string         resume scan using resume.cfg\n   -e, -exclude string[]  exclude host matching specified filter ('cdn', 'private-ips', cidr, ip, regex)\n\nCONFIGURATION:\n   -r, -resolvers string[]       list of custom resolver (file or comma separated)\n   -d, -depth int                maximum depth to crawl (default 3)\n   -jc, -js-crawl                enable endpoint parsing / crawling in javascript file\n   -jsl, -jsluice                enable jsluice parsing in javascript file (memory intensive)\n   -ct, -crawl-duration value    maximum duration to crawl the target for (s, m, h, d) (default s)\n   -kf, -known-files string      enable crawling of known files (all,robotstxt,sitemapxml), a minimum depth of 3 is required to ensure all known files are properly crawled.\n   -mrs, -max-response-size int  maximum response size to read (default 4194304)\n   -timeout int                  time to wait for request in seconds (default 10)\n   -aff, -automatic-form-fill    enable automatic form filling (experimental)\n   -fx, -form-extraction         extract form, input, textarea & select elements in jsonl output\n   -retry int                    number of times to retry the request (default 1)\n   -proxy string                 http/socks5 proxy to use\n   -td, -tech-detect             enable technology detection\n   -H, -headers string[]         custom header/cookie to include in all http request in header:value format (file)\n   -config string                path to the katana configuration file\n   -fc, -form-config string      path to custom form configuration file\n   -flc, -field-config string    path to custom field configuration file\n   -s, -strategy string          Visit strategy (depth-first, breadth-first) (default \"depth-first\")\n   -iqp, -ignore-query-params    Ignore crawling same path with different query-param values\n   -fsu, -filter-similar         filter crawling of similar looking URLs (e.g., /users/123 and /users/456)\n   -fst, -filter-similar-threshold int  number of distinct values before a path position is treated as parameter (default 10)\n   -tlsi, -tls-impersonate       enable experimental client hello (ja3) tls randomization\n   -dr, -disable-redirects       disable following redirects (default false)\n   -kb, -knowledge-base          enable knowledge base classification\n\nDEBUG:\n   -health-check, -hc        run diagnostic check up\n   -elog, -error-log string  file to write sent requests error log\n   -pprof-server             enable pprof server\n\nHEADLESS:\n   -hl, -headless                    enable headless hybrid crawling (experimental)\n   -sc, -system-chrome               use local installed chrome browser instead of katana installed\n   -sb, -show-browser                show the browser on the screen with headless mode\n   -ho, -headless-options string[]   start headless chrome with additional options\n   -nos, -no-sandbox                 start headless chrome in --no-sandbox mode\n   -cdd, -chrome-data-dir string     path to store chrome browser data\n   -scp, -system-chrome-path string  use specified chrome browser for headless crawling\n   -noi, -no-incognito               start headless chrome without incognito mode\n   -cwu, -chrome-ws-url string       use chrome browser instance launched elsewhere with the debugger listening at this URL\n   -xhr, -xhr-extraction             extract xhr request url,method in jsonl output\n   -csp, -captcha-solver-provider string  captcha solver provider (e.g. capsolver)\n   -csk, -captcha-solver-key string       captcha solver provider api key\n\nSCOPE:\n   -cs, -crawl-scope string[]       in scope url regex to be followed by crawler\n   -cos, -crawl-out-scope string[]  out of scope url regex to be excluded by crawler\n   -fs, -field-scope string         pre-defined scope field (dn,rdn,fqdn) or custom regex (e.g., '(company-staging.io|company.com)') (default \"rdn\")\n   -ns, -no-scope                   disables host based default scope\n   -do, -display-out-scope          display external endpoint from scoped crawling\n\nFILTER:\n   -mr, -match-regex string[]             regex or list of regex to match on output url (cli, file)\n   -fr, -filter-regex string[]            regex or list of regex to filter on output url (cli, file)\n   -f, -field string                      field to display in output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir) (Deprecated: use -output-template instead)\n   -sf, -store-field string               field to store in per-host output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir)\n   -em, -extension-match string[]         match output for given extension (eg, -em php,html,js)\n   -ef, -extension-filter string[]        filter output for given extension (eg, -ef png,css)\n   -ndef, -no-default-ext-filter bool     remove default extensions from the filter list\n   -mdc, -match-condition string          match response with dsl based condition\n   -fdc, -filter-condition string         filter response with dsl based condition\n   -duf, -disable-unique-filter           disable duplicate content filtering\n   -fpt, -filter-page-type string[]      filter response with page type (e.g. error,captcha,parked)\n\nRATE-LIMIT:\n   -c, -concurrency int          number of concurrent fetchers to use (default 10)\n   -p, -parallelism int          number of concurrent inputs to process (default 10)\n   -rd, -delay int               request delay between each request in seconds\n   -rl, -rate-limit int          maximum requests to send per second (default 150)\n   -rlm, -rate-limit-minute int  maximum number of requests to send per minute\n\nUPDATE:\n   -up, -update                 update katana to latest version\n   -duc, -disable-update-check  disable automatic katana update check\n\nOUTPUT:\n   -o, -output string                file to write output to\n   -ot, -output-template string      custom output template\n   -sr, -store-response              store http requests/responses\n   -srd, -store-response-dir string  store http requests/responses to custom directory\n   -ncb, -no-clobber                 do not overwrite output file\n   -sfd, -store-field-dir string     store per-host field to custom directory\n   -or, -omit-raw                    omit raw requests/responses from jsonl output\n   -ob, -omit-body                   omit response body from jsonl output\n   -lof, -list-output-fields         list available fields for jsonl output format\n   -eof, -exclude-output-fields      exclude fields from jsonl output\n   -j, -jsonl                        write output in jsonl format\n   -nc, -no-color                    disable output content coloring (ANSI escape codes)\n   -silent                           display output only\n   -v, -verbose                      display verbose output\n   -debug                            display debug output\n   -version                          display project version\n```\n\n## Running Katana\n\n### Input for katana\n\n**katana** requires **url** or **endpoint** to crawl and accepts single or multiple inputs.\n\nInput URL can be provided using `-u` option, and multiple values can be provided using comma-separated input, similarly **file** input is supported using `-list` option and additionally piped input (stdin) is also supported.\n\n#### URL Input\n\n```sh\nkatana -u https://tesla.com\n```\n\n#### Multiple URL Input (comma-separated)\n\n```sh\nkatana -u https://tesla.com,https://google.com\n```\n\n#### List Input\n```bash\n$ cat url_list.txt\n\nhttps://tesla.com\nhttps://google.com\n```\n\n```\nkatana -list url_list.txt\n```\n\n#### STDIN (piped) Input\n\n```sh\necho https://tesla.com | katana\n```\n\n```sh\ncat domains | httpx | katana\n```\n\nExample running katana -\n\n```console\nkatana -u https://youtube.com\n\n   __        __                \n  / /_____ _/ /____ ____  ___ _\n /  '_/ _  / __/ _  / _ \\/ _  /\n/_/\\_\\\\_,_/\\__/\\_,_/_//_/\\_,_/ v0.0.1                     \n\n      projectdiscovery.io\n\n[WRN] Use with caution. You are responsible for your actions.\n[WRN] Developers assume no liability and are not responsible for any misuse or damage.\nhttps://www.youtube.com/\nhttps://www.youtube.com/about/\nhttps://www.youtube.com/about/press/\nhttps://www.youtube.com/about/copyright/\nhttps://www.youtube.com/t/contact_us/\nhttps://www.youtube.com/creators/\nhttps://www.youtube.com/ads/\nhttps://www.youtube.com/t/terms\nhttps://www.youtube.com/t/privacy\nhttps://www.youtube.com/about/policies/\nhttps://www.youtube.com/howyoutubeworks?utm_campaign=ytgen&utm_source=ythp&utm_medium=LeftNav&utm_content=txt&u=https%3A%2F%2Fwww.youtube.com%2Fhowyoutubeworks%3Futm_source%3Dythp%26utm_medium%3DLeftNav%26utm_campaign%3Dytgen\nhttps://www.youtube.com/new\nhttps://m.youtube.com/\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/desktop_polymer.vflset/desktop_polymer.js\nhttps://www.youtube.com/s/desktop/4965577f/cssbin/www-main-desktop-home-page-skeleton.css\nhttps://www.youtube.com/s/desktop/4965577f/cssbin/www-onepick.css\nhttps://www.youtube.com/s/_/ytmainappweb/_/ss/k=ytmainappweb.kevlar_base.0Zo5FUcPkCg.L.B1.O/am=gAE/d=0/rs=AGKMywG5nh5Qp-BGPbOaI1evhF5BVGRZGA\nhttps://www.youtube.com/opensearch?locale=en_GB\nhttps://www.youtube.com/manifest.webmanifest\nhttps://www.youtube.com/s/desktop/4965577f/cssbin/www-main-desktop-watch-page-skeleton.css\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/web-animations-next-lite.min.vflset/web-animations-next-lite.min.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/custom-elements-es5-adapter.vflset/custom-elements-es5-adapter.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/webcomponents-sd.vflset/webcomponents-sd.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/intersection-observer.min.vflset/intersection-observer.min.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/scheduler.vflset/scheduler.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/www-i18n-constants-en_GB.vflset/www-i18n-constants.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/www-tampering.vflset/www-tampering.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/spf.vflset/spf.js\nhttps://www.youtube.com/s/desktop/4965577f/jsbin/network.vflset/network.js\nhttps://www.youtube.com/howyoutubeworks/\nhttps://www.youtube.com/trends/\nhttps://www.youtube.com/jobs/\nhttps://www.youtube.com/kids/\n```\n\n\n## Crawling Mode\n\n### Standard Mode\n\nStandard crawling modality uses the standard go http library under the hood to handle HTTP requests/responses. This modality is much faster as it doesn't have the browser overhead. Still, it analyzes HTTP responses body as is, without any javascript or DOM rendering, potentially missing post-dom-rendered endpoints or asynchronous endpoint calls that might happen in complex web applications depending, for example, on browser-specific events.\n\n### Headless Mode\n\nHeadless mode hooks internal headless calls to handle HTTP requests/responses directly within the browser context. This offers two advantages:\n- The HTTP fingerprint (TLS and user agent) fully identify the client as a legitimate browser\n- Better coverage since the endpoints are discovered analyzing the standard raw response, as in the previous modality, and also the browser-rendered one with javascript enabled.\n\nHeadless crawling is optional and can be enabled using `-headless` option.\n\nHere are other headless CLI options -\n\n```console\nkatana -h headless\n\nFlags:\nHEADLESS:\n   -hl, -headless                    enable headless hybrid crawling (experimental)\n   -sc, -system-chrome               use local installed chrome browser instead of katana installed\n   -sb, -show-browser                show the browser on the screen with headless mode\n   -ho, -headless-options string[]   start headless chrome with additional options\n   -nos, -no-sandbox                 start headless chrome in --no-sandbox mode\n   -cdd, -chrome-data-dir string     path to store chrome browser data\n   -scp, -system-chrome-path string  use specified chrome browser for headless crawling\n   -noi, -no-incognito               start headless chrome without incognito mode\n   -cwu, -chrome-ws-url string       use chrome browser instance launched elsewhere with the debugger listening at this URL\n   -xhr, -xhr-extraction             extract xhr requests\n   -csp, -captcha-solver-provider string  captcha solver provider (e.g. capsolver)\n   -csk, -captcha-solver-key string       captcha solver provider api key\n```\n\n*`-no-sandbox`*\n----\n\nRuns headless chrome browser with **no-sandbox** option, useful when running as root user.\n\n```console\nkatana -u https://tesla.com -headless -no-sandbox\n```\n\n*`-no-incognito`*\n----\n\nRuns headless chrome browser without incognito mode, useful when using the local browser.\n\n```console\nkatana -u https://tesla.com -headless -no-incognito\n```\n\n*`-headless-options`*\n----\n\nWhen crawling in headless mode, additional chrome options can be specified using `-headless-options`, for example -\n\n\n```console\nkatana -u https://tesla.com -headless -system-chrome -headless-options --disable-gpu,proxy-server=http://127.0.0.1:8080\n```\n\n\n### Captcha Solving\n\nKatana supports automatic captcha detection and solving during headless crawling. When a captcha page is encountered, katana identifies the captcha provider, solves it via an external service, and continues crawling.\n\nSupported captcha types: **reCAPTCHA v2**, **reCAPTCHA v3**, **reCAPTCHA Enterprise**, **Cloudflare Turnstile**, **hCaptcha**\n\n*`-captcha-solver-provider`*\n----\n\nOption to specify the captcha solver provider. Currently supported: `capsolver`.\n\n*`-captcha-solver-key`*\n----\n\nAPI key for the captcha solver provider.\n\n```console\nkatana -u https://example.com -headless -csp capsolver -csk YOUR_API_KEY\n```\n\nThe provider and key can also be set via environment variables:\n\n```console\nexport CAPTCHA_SOLVER_PROVIDER=capsolver\nexport CAPTCHA_SOLVER_KEY=YOUR_API_KEY\nkatana -u https://example.com -headless\n```\n\n## Scope Control\n\nCrawling can be endless if not scoped, as such katana comes with multiple support to define the crawl scope.\n\n*`-field-scope`*\n----\nMost handy option to define scope with predefined field name, `rdn` being default option for field scope.\n\n   - `rdn` - crawling scoped to root domain name and all subdomains (e.g. `*example.com`) (default)\n   - `fqdn` - crawling scoped to given sub(domain) (e.g. `www.example.com` or `api.example.com`)\n   - `dn` - crawling scoped to domain name keyword (e.g. `example`)\n\n```console\nkatana -u https://tesla.com -fs dn\n```\n\n\n*`-crawl-scope`*\n------\n\nFor advanced scope control, `-cs` option can be used that comes with **regex** support.\n\n```console\nkatana -u https://tesla.com -cs login\n```\n\nFor multiple in scope rules, file input with multiline string / regex can be passed.\n\n```bash\n$ cat in_scope.txt\n\nlogin/\nadmin/\napp/\nwordpress/\n```\n\n```console\nkatana -u https://tesla.com -cs in_scope.txt\n```\n\n\n*`-crawl-out-scope`*\n-----\n\nFor defining what not to crawl, `-cos` option can be used and also support **regex** input.\n\n```console\nkatana -u https://tesla.com -cos logout\n```\n\nFor multiple out of scope rules, file input with multiline string / regex can be passed.\n\n```bash\n$ cat out_of_scope.txt\n\n/logout\n/log_out\n```\n\n```console\nkatana -u https://tesla.com -cos out_of_scope.txt\n```\n\n*`-no-scope`*\n----\n\nKatana is default to scope `*.domain`, to disable this `-ns` option can be used and also to crawl the internet.\n\n```console\nkatana -u https://tesla.com -ns\n```\n\n*`-display-out-scope`*\n----\n\nAs default, when scope option is used, it also applies for the links to display as output, as such **external URLs are default to exclude** and to overwrite this behavior, `-do` option can be used to display all the external URLs that exist in targets scoped URL / Endpoint.\n\n```\nkatana -u https://tesla.com -do\n```\n\nHere is all the CLI options for the scope control -\n\n\n```console\nkatana -h scope\n\nFlags:\nSCOPE:\n   -cs, -crawl-scope string[]       in scope url regex to be followed by crawler\n   -cos, -crawl-out-scope string[]  out of scope url regex to be excluded by crawler\n   -fs, -field-scope string         pre-defined scope field (dn,rdn,fqdn) (default \"rdn\")\n   -ns, -no-scope                   disables host based default scope\n   -do, -display-out-scope          display external endpoint from scoped crawling\n```\n\n\n## Crawler Configuration\n\nKatana comes with multiple options to configure and control the crawl as the way we want.\n\n*`-depth`*\n----\n\nOption to define the `depth` to follow the urls for crawling, the more depth the more number of endpoint being crawled + time for crawl.\n\n```\nkatana -u https://tesla.com -d 5\n```\n\n*`-js-crawl`*\n----\n\nOption to enable JavaScript file parsing + crawling the endpoints discovered in JavaScript files, disabled as default.\n\n```\nkatana -u https://tesla.com -jc\n```\n\n*`-crawl-duration`*\n----\n\nOption to predefined crawl duration, disabled as default.\n\n```\nkatana -u https://tesla.com -ct 2\n```\n\n*`-known-files`*\n----\nOption to enable crawling `robots.txt` and `sitemap.xml` file, disabled as default.\n\n```\nkatana -u https://tesla.com -kf robotstxt,sitemapxml\n```\n\n*`-automatic-form-fill`*\n----\n\nOption to enable automatic form filling for known / unknown fields, known field values can be customized as needed by updating form config file at `$HOME/.config/katana/form-config.yaml`.\n\nAutomatic form filling is experimental feature.\n\n```\nkatana -u https://tesla.com -aff\n```\n\n*`-filter-similar`*\n----\n\nOption to filter crawling of similar looking URLs by normalizing variable path segments. This detects IDs, UUIDs, hashes, dates, and other dynamic values, and also learns repeating patterns at runtime. For example, `/users/123` and `/users/456` are treated as the same endpoint.\n\n```\nkatana -u https://tesla.com -fsu\n```\n\nThe promotion threshold (how many distinct values at a path position before it's treated as a parameter) can be tuned with `-fst`. Lower values are more aggressive (fewer URLs crawled), higher values are more permissive. Default is `10`.\n\n```\nkatana -u https://tesla.com -fsu -fst 5\n```\n\n## Authenticated Crawling\n\nAuthenticated crawling involves including custom headers or cookies in HTTP requests to access protected resources. These headers provide authentication or authorization information, allowing you to crawl authenticated content / endpoint. You can specify headers directly in the command line or provide them as a file with katana to perform authenticated crawling.\n\n> **Note**: User needs to be manually perform the authentication and export the session cookie / header to file to use with katana.\n\n*`-headers`*\n----\n\nOption to add a custom header or cookie to the request. \n> Syntax of [headers](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2) in the HTTP specification\n\nHere is an example of adding a cookie to the request:\n```\nkatana -u https://tesla.com -H 'Cookie: usrsess=AmljNrESo'\n```\n\nIt is also possible to supply headers or cookies as a file. For example:\n\n```\n$ cat cookie.txt\n\nCookie: PHPSESSIONID=XXXXXXXXX\nX-API-KEY: XXXXX\nTOKEN=XX\n```\n\n```\nkatana -u https://tesla.com -H cookie.txt\n```\n\n\nThere are more options to configure when needed, here is all the config related CLI options - \n\n```console\nkatana -h config\n\nFlags:\nCONFIGURATION:\n   -r, -resolvers string[]       list of custom resolver (file or comma separated)\n   -d, -depth int                maximum depth to crawl (default 3)\n   -jc, -js-crawl                enable endpoint parsing / crawling in javascript file\n   -ct, -crawl-duration int      maximum duration to crawl the target for\n   -kf, -known-files string      enable crawling of known files (all,robotstxt,sitemapxml)\n   -mrs, -max-response-size int  maximum response size to read (default 9223372036854775807)\n   -timeout int                  time to wait for request in seconds (default 10)\n   -aff, -automatic-form-fill    enable automatic form filling (experimental)\n   -fx, -form-extraction         enable extraction of form, input, textarea & select elements\n   -retry int                    number of times to retry the request (default 1)\n   -proxy string                 http/socks5 proxy to use\n   -H, -headers string[]         custom header/cookie to include in request\n   -config string                path to the katana configuration file\n   -fc, -form-config string      path to custom form configuration file\n   -flc, -field-config string    path to custom field configuration file\n   -s, -strategy string          Visit strategy (depth-first, breadth-first) (default \"depth-first\")\n   -iqp, -ignore-query-params    Ignore crawling same path with different query-param values\n   -fsu, -filter-similar         filter crawling of similar looking URLs (e.g., /users/123 and /users/456)\n   -fst, -filter-similar-threshold int  number of distinct values before a path position is treated as parameter (default 10)\n```\n\n### Connecting to Active Browser Session\n\nKatana can also connect to active browser session where user is already logged in and authenticated. and use it for crawling. The only requirement for this is to start browser with remote debugging enabled.\n\nHere is an example of starting chrome browser with remote debugging enabled and using it with katana -\n\n**step 1) First Locate path of chrome executable**\n\n| Operating System | Chromium Executable Location | Google Chrome Executable Location |\n|------------------|------------------------------|-----------------------------------|\n| Windows (64-bit) | `C:\\Program Files (x86)\\Google\\Chromium\\Application\\chrome.exe` | `C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe` |\n| Windows (32-bit) | `C:\\Program Files\\Google\\Chromium\\Application\\chrome.exe` | `C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe` |\n| macOS | `/Applications/Chromium.app/Contents/MacOS/Chromium` | `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome` |\n| Linux | `/usr/bin/chromium` | `/usr/bin/google-chrome` |\n\n**step 2) Start chrome with remote debugging enabled and it will return websocker url. For example, on MacOS, you can start chrome with remote debugging enabled using following command** -\n\n```console\n$ /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222\n\n\nDevTools listening on ws://127.0.0.1:9222/devtools/browser/c5316c9c-19d6-42dc-847a-41d1aeebf7d6\n```\n\n> Now login to the website you want to crawl and keep the browser open.\n\n**step 3) Now use the websocket url with katana to connect to the active browser session and crawl the website**\n\n```console\nkatana -headless -u https://tesla.com -cwu ws://127.0.0.1:9222/devtools/browser/c5316c9c-19d6-42dc-847a-41d1aeebf7d6 -no-incognito\n```\n\n> **Note**: you can use `-cdd` option to specify custom chrome data directory to store browser data and cookies but that does not save session data if cookie is set to `Session` only or expires after certain time.\n\n\n## Filters\n\n*`-field`*\n----\n\n> [!WARNING]\n> Deprecated: use [**`-output-template`**](#-output-template) instead. The field flag is still supported for backward compatibility.\n\nKatana comes with built in fields that can be used to filter the output for the desired information, `-f` option can be used to specify any of the available fields.\n\n```\n   -f, -field string  field to display in output (url,path,fqdn,rdn,rurl,qurl,qpath,file,key,value,kv,dir,udir)\n```\n\nHere is a table with examples of each field and expected output when used - \n\n\n| FIELD   | DESCRIPTION                 | EXAMPLE                                                      |\n| ------- | --------------------------- | ------------------------------------------------------------ |\n| `url`   | URL Endpoint                | `https://admin.projectdiscovery.io/admin/login?user=admin&password=admin` |\n| `qurl`  | URL including query param   | `https://admin.projectdiscovery.io/admin/login.php?user=admin&password=admin` |\n| `qpath` | Path including query param  | `/login?user=admin&password=admin`                           |\n| `path`  | URL Path                    | `https://admin.projectdiscovery.io/admin/login`              |\n| `fqdn`  | Fully Qualified Domain name | `admin.projectdiscovery.io`                                  |\n| `rdn`   | Root Domain name            | `projectdiscovery.io`                                        |\n| `rurl`  | Root URL                    | `https://admin.projectdiscovery.io`                          |\n| `ufile` | URL with File               | `https://admin.projectdiscovery.io/login.js`                 |\n| `file`  | Filename in URL             | `login.php`                                                  |\n| `key`   | Parameter keys in URL       | `user,password`                                              |\n| `value` | Parameter values in URL     | `admin,admin`                                                |\n| `kv`    | Keys=Values in URL          | `user=admin&password=admin`                                  |\n| `dir`   | URL Directory name          | `/admin/`                                                    |\n| `udir`  | URL with Directory          | `https://admin.projectdiscovery.io/admin/`                   |\n\nHere is an example of using field option to only display all the urls with query parameter in it -\n\n```\nkatana -u https://tesla.com -f qurl -silent\n\nhttps://shop.tesla.com/en_au?redirect=no\nhttps://shop.tesla.com/en_nz?redirect=no\nhttps://shop.tesla.com/product/men_s-raven-lightweight-zip-up-bomber-jacket?sku=1740250-00-A\nhttps://shop.tesla.com/product/tesla-shop-gift-card?sku=1767247-00-A\nhttps://shop.tesla.com/product/men_s-chill-crew-neck-sweatshirt?sku=1740176-00-A\nhttps://www.tesla.com/about?redirect=no\nhttps://www.tesla.com/about/legal?redirect=no\nhttps://www.tesla.com/findus/list?redirect=no\n```\n\n### Custom Fields\n\nYou can create custom fields to extract and store specific information from page responses using regex rules. These custom fields are defined using a YAML config file and are loaded from the default location at `$HOME/.config/katana/field-config.yaml`. Alternatively, you can use the `-flc` option to load a custom field config file from a different location.\nHere is example custom field.\n\n```yaml\n- name: email\n  type: regex\n  regex:\n  - '([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\\.[a-zA-Z0-9_-]+)'\n  - '([a-zA-Z0-9+._-]+@[a-zA-Z0-9._-]+\\.[a-zA-Z0-9_-]+)'\n\n- name: phone\n  type: regex\n  regex:\n  - '\\d{3}-\\d{8}|\\d{4}-\\d{7}'\n```\n\nWhen defining custom fields, following attributes are supported:\n\n- **name** (required)\n\n> The value of **name** attribute is used as the `-field` cli option value.\n\n- **type** (required)\n\n> The type of custom attribute, currently supported option - `regex` \n\n- **part** (optional)\n\n> The part of the response to extract the information from. The default value is `response`, which includes both the header and body. Other possible values are `header` and `body`.\n\n- group (optional)\n\n> You can use this attribute to select a specific matched group in regex, for example: `group: 1`\n\n#### Running katana using custom field:\n\n```console\nkatana -u https://tesla.com -f email,phone\n```\n\n*`-store-field`*\n---\n\nTo compliment `field` option which is useful to filter output at run time, there is `-sf, -store-fields` option which works exactly like field option except instead of filtering, it stores all the information on the disk under `katana_field` directory sorted by target url. Use `-sfd` or `-store-field-dir` to store data in a different location.\n\n```\nkatana -u https://tesla.com -sf key,fqdn,qurl -silent\n```\n\n```bash\n$ ls katana_field/\n\nhttps_www.tesla.com_fqdn.txt\nhttps_www.tesla.com_key.txt\nhttps_www.tesla.com_qurl.txt\n```\n\nThe `-store-field` option can be useful for collecting information to build a targeted wordlist for various purposes, including but not limited to:\n\n- Identifying the most commonly used parameters\n- Discovering frequently used paths\n- Finding commonly used files\n- Identifying related or unknown subdomains\n\n### Katana Filters\n\n*`-extension-match`*\n---\n\nCrawl output can be easily matched for specific extension using `-em` option to ensure to display only output containing given extension.\n\n```\nkatana -u https://tesla.com -silent -em js,jsp,json\n```\n\n*`-extension-filter`*\n---\n\nCrawl output can be easily filtered for specific extension using `-ef` option which ensure to remove all the urls containing given extension.\n\n```\nkatana -u https://tesla.com -silent -ef css,txt,md\n\n```\n*`-no-default-ext-filter`*\n---\n\nKatana filters several extensions by default. This can be disabled with the `-ndef` option.\n\n```\nkatana -u https://tesla.com -silent -ndef\n```\n\n*`-match-regex`*\n---\nThe `-match-regex` or `-mr` flag allows you to filter output URLs using regular expressions. When using this flag, only URLs that match the specified regular expression will be printed in the output.\n\n```\nkatana -u https://tesla.com -mr 'https://shop\\.tesla\\.com/*' -silent\n```\n*`-filter-regex`*\n---\nThe `-filter-regex` or `-fr` flag allows you to filter output URLs using regular expressions. When using this flag, it will skip the URLs that are match the specified regular expression.\n\n```\nkatana -u https://tesla.com -fr 'https://www\\.tesla\\.com/*' -silent\n```\n\n### Advance Filtering\n\nKatana supports DSL-based expressions for advanced matching and filtering capabilities:\n\n- To match endpoints with a 200 status code:\n```shell\nkatana -u https://www.hackerone.com -mdc 'status_code == 200'\n```\n- To match endpoints that contain \"default\" and have a status code other than 403:\n```shell\nkatana -u https://www.hackerone.com -mdc 'contains(endpoint, \"default\") && status_code != 403'\n```\n- To match endpoints with PHP technologies:\n```shell\nkatana -u https://www.hackerone.com -mdc 'contains(to_lower(technologies), \"php\")'\n```\n- To filter out endpoints running on Cloudflare:\n```shell\nkatana -u https://www.hackerone.com -fdc 'contains(to_lower(technologies), \"cloudflare\")'\n```\nDSL functions can be applied to any keys in the jsonl output. For more information on available DSL functions, please visit the [dsl project](https://github.com/projectdiscovery/dsl).\n\nHere are additional filter options -\n\n```console\nkatana -h filter\n\nFlags:\nFILTER:\n   -mr, -match-regex string[]             regex or list of regex to match on output url (cli, file)\n   -fr, -filter-regex string[]            regex or list of regex to filter on output url (cli, file)\n   -f, -field string                      field to display in output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir)\n   -sf, -store-field string               field to store in per-host output (url,path,fqdn,rdn,rurl,qurl,qpath,file,ufile,key,value,kv,dir,udir)\n   -em, -extension-match string[]         match output for given extension (eg, -em php,html,js)\n   -ef, -extension-filter string[]        filter output for given extension (eg, -ef png,css)\n   -ndef, -no-default-ext-filter bool     remove default extensions from the filter list\n   -mdc, -match-condition string          match response with dsl based condition\n   -fdc, -filter-condition string         filter response with dsl based condition\n   -duf, -disable-unique-filter           disable duplicate content filtering\n```\n\n\n## Rate Limit\n\nIt's easy to get blocked / banned while crawling if not following target websites limits, katana comes with multiple option to tune the crawl to go as fast / slow we want.\n\n*`-delay`*\n-----\n\noption to introduce a delay in seconds between each new request katana makes while crawling, disabled as default.\n\n```\nkatana -u https://tesla.com -delay 20\n```\n\n*`-concurrency`*\n-----\noption to control the number of urls per target to fetch at the same time.\n\n```\nkatana -u https://tesla.com -c 20\n```\n\n\n*`-parallelism`*\n-----\noption to define number of target to process at same time from list input.\n\n```\nkatana -u https://tesla.com -p 20\n```\n\n*`-rate-limit`*\n-----\noption to use to define max number of request can go out per second.\n\n```\nkatana -u https://tesla.com -rl 100\n```\n\n*`-rate-limit-minute`*\n-----\noption to use to define max number of request can go out per minute.\n\n```\nkatana -u https://tesla.com -rlm 500\n```\n\nHere is all long / short CLI options for rate limit control -\n\n```console\nkatana -h rate-limit\n\nFlags:\nRATE-LIMIT:\n   -c, -concurrency int          number of concurrent fetchers to use (default 10)\n   -p, -parallelism int          number of concurrent inputs to process (default 10)\n   -rd, -delay int               request delay between each request in seconds\n   -rl, -rate-limit int          maximum requests to send per second (default 150)\n   -rlm, -rate-limit-minute int  maximum number of requests to send per minute\n```\n\n## Output\n\nKatana support both file output in plain text format as well as JSON which includes additional information like, `source`, `tag`, and `attribute` name to co-related the discovered endpoint.\n\n*`-output`*\n---\n\nBy default, katana outputs the crawled endpoints in plain text format. The results can be written to a file by using the -output option.\n\n\n```console\nkatana -u https://example.com -no-scope -output example_endpoints.txt\n```\n\n*`-output-template`*\n---\n\nThe `-output-template` option allows you to customize the output format using template, providing flexibility in defining the output structure. This option replaces the deprecated `-field` flag for filtering output. Instead of relying on predefined fields, you can specify a custom template directly in the command line to control how the extracted data is presented.\n\nExample of using the `-output-template` option:\n\n```sh\nkatana -u https://example.com -output-template '{{email}} - {{url}}'\n```\n\nIn this example, `email` represents a [custom field](#custom-fields) that extracts and displays email addresses found within the source `url`.\n\n> [!NOTE]\n> If a specified field does not exist or does not contain a value, it will simply be omitted from the output.\n\nThis option can effectively structure the output in a way that best suits your use case, making data extraction more intuitive and customizable.\n\n*`-jsonl`*\n---\n\n```console\nkatana -u https://example.com -jsonl | jq .\n```\n\n```json\n{\n  \"timestamp\": \"2023-03-20T16:23:58.027559+05:30\",\n  \"request\": {\n    \"method\": \"GET\",\n    \"endpoint\": \"https://example.com\",\n    \"raw\": \"GET / HTTP/1.1\\r\\nHost: example.com\\r\\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36\\r\\nAccept-Encoding: gzip\\r\\n\\r\\n\"\n  },\n  \"response\": {\n    \"status_code\": 200,\n    \"headers\": {\n      \"accept_ranges\": \"bytes\",\n      \"expires\": \"Mon, 27 Mar 2023 10:53:58 GMT\",\n      \"last_modified\": \"Thu, 17 Oct 2019 07:18:26 GMT\",\n      \"content_type\": \"text/html; charset=UTF-8\",\n      \"server\": \"ECS (dcb/7EA3)\",\n      \"vary\": \"Accept-Encoding\",\n      \"etag\": \"\\\"3147526947\\\"\",\n      \"cache_control\": \"max-age=604800\",\n      \"x_cache\": \"HIT\",\n      \"date\": \"Mon, 20 Mar 2023 10:53:58 GMT\",\n      \"age\": \"331239\"\n    },\n    \"body\": \"<!doctype html>\\n<html>\\n<head>\\n    <title>Example Domain</title>\\n\\n    <meta charset=\\\"utf-8\\\" />\\n    <meta http-equiv=\\\"Content-type\\\" content=\\\"text/html; charset=utf-8\\\" />\\n    <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1\\\" />\\n    <style type=\\\"text/css\\\">\\n    body {\\n        background-color: #f0f0f2;\\n        margin: 0;\\n        padding: 0;\\n        font-family: -apple-system, system-ui, BlinkMacSystemFont, \\\"Segoe UI\\\", \\\"Open Sans\\\", \\\"Helvetica Neue\\\", Helvetica, Arial, sans-serif;\\n        \\n    }\\n    div {\\n        width: 600px;\\n        margin: 5em auto;\\n        padding: 2em;\\n        background-color: #fdfdff;\\n        border-radius: 0.5em;\\n        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\\n    }\\n    a:link, a:visited {\\n        color: #38488f;\\n        text-decoration: none;\\n    }\\n    @media (max-width: 700px) {\\n        div {\\n            margin: 0 auto;\\n            width: auto;\\n        }\\n    }\\n    </style>    \\n</head>\\n\\n<body>\\n<div>\\n    <h1>Example Domain</h1>\\n    <p>This domain is for use in illustrative examples in documents. You may use this\\n    domain in literature without prior coordination or asking for permission.</p>\\n    <p><a href=\\\"https://www.iana.org/domains/example\\\">More information...</a></p>\\n</div>\\n</body>\\n</html>\\n\",\n    \"technologies\": [\n      \"Azure\",\n      \"Amazon ECS\",\n      \"Amazon Web Services\",\n      \"Docker\",\n      \"Azure CDN\"\n    ],\n    \"raw\": \"HTTP/1.1 200 OK\\r\\nContent-Length: 1256\\r\\nAccept-Ranges: bytes\\r\\nAge: 331239\\r\\nCache-Control: max-age=604800\\r\\nContent-Type: text/html; charset=UTF-8\\r\\nDate: Mon, 20 Mar 2023 10:53:58 GMT\\r\\nEtag: \\\"3147526947\\\"\\r\\nExpires: Mon, 27 Mar 2023 10:53:58 GMT\\r\\nLast-Modified: Thu, 17 Oct 2019 07:18:26 GMT\\r\\nServer: ECS (dcb/7EA3)\\r\\nVary: Accept-Encoding\\r\\nX-Cache: HIT\\r\\n\\r\\n<!doctype html>\\n<html>\\n<head>\\n    <title>Example Domain</title>\\n\\n    <meta charset=\\\"utf-8\\\" />\\n    <meta http-equiv=\\\"Content-type\\\" content=\\\"text/html; charset=utf-8\\\" />\\n    <meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1\\\" />\\n    <style type=\\\"text/css\\\">\\n    body {\\n        background-color: #f0f0f2;\\n        margin: 0;\\n        padding: 0;\\n        font-family: -apple-system, system-ui, BlinkMacSystemFont, \\\"Segoe UI\\\", \\\"Open Sans\\\", \\\"Helvetica Neue\\\", Helvetica, Arial, sans-serif;\\n        \\n    }\\n    div {\\n        width: 600px;\\n        margin: 5em auto;\\n        padding: 2em;\\n        background-color: #fdfdff;\\n        border-radius: 0.5em;\\n        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\\n    }\\n    a:link, a:visited {\\n        color: #38488f;\\n        text-decoration: none;\\n    }\\n    @media (max-width: 700px) {\\n        div {\\n            margin: 0 auto;\\n            width: auto;\\n        }\\n    }\\n    </style>    \\n</head>\\n\\n<body>\\n<div>\\n    <h1>Example Domain</h1>\\n    <p>This domain is for use in illustrative examples in documents. You may use this\\n    domain in literature without prior coordination or asking for permission.</p>\\n    <p><a href=\\\"https://www.iana.org/domains/example\\\">More information...</a></p>\\n</div>\\n</body>\\n</html>\\n\"\n  }\n}\n```\n\n*`-store-response`*\n----\n\nThe `-store-response` option allows for writing all crawled endpoint requests and responses to a text file. When this option is used, text files including the request and response will be written to the **katana_response** directory. If you would like to specify a custom directory, you can use the `-store-response-dir` option.\n\n```console\nkatana -u https://example.com -no-scope -store-response\n```\n\n```bash\n$ cat katana_response/index.txt\n\nkatana_response/example.com/327c3fda87ce286848a574982ddd0b7c7487f816.txt https://example.com (200 OK)\nkatana_response/www.iana.org/bfc096e6dd93b993ca8918bf4c08fdc707a70723.txt http://www.iana.org/domains/reserved (200 OK)\n```\n\n**Note:**\n\n*`-store-response` option is not supported in `-headless` mode.*\n\n*`-list-output-fields`*\n----\n\nThe `-list-output-fields` or `-lof` flag displays all available fields that can be used in JSONL output format. This is useful for understanding what data is available when using custom output templates or when excluding specific fields.\n\n```console\nkatana -lof\n```\n\n*`-exclude-output-fields`*\n----\n\nThe `-exclude-output-fields` or `-eof` flag allows you to exclude specific fields from the JSONL output. This is useful for reducing output size or focusing on specific data by removing unwanted fields.\n\n```console\nkatana -u https://example.com -jsonl -eof raw,body\n```\n\nHere are additional CLI options related to output -\n\n```console\nkatana -h output\n\nOUTPUT:\n   -o, -output string                file to write output to\n   -sr, -store-response              store http requests/responses\n   -srd, -store-response-dir string  store http requests/responses to custom directory\n   -lof, -list-output-fields         list available fields for jsonl output format\n   -eof, -exclude-output-fields      exclude fields from jsonl output\n   -j, -json                         write output in JSONL(ines) format\n   -nc, -no-color                    disable output content coloring (ANSI escape codes)\n   -silent                           display output only\n   -v, -verbose                      display verbose output\n   -version                          display project version\n```\n\n## Katana as a library\n`katana` can be used as a library by creating an instance of the `Option` struct and populating it with the same options that would be specified via CLI. Using the options you can create `crawlerOptions` and so standard or hybrid `crawler`.\n`crawler.Crawl` method should be called to crawl the input.\n\n```go\npackage main\n\nimport (\n\t\"math\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/standard\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n)\n\nfunc main() {\n\toptions := &types.Options{\n\t\tMaxDepth:     3,             // Maximum depth to crawl\n\t\tFieldScope:   \"rdn\",         // Crawling Scope Field\n\t\tBodyReadSize: math.MaxInt,   // Maximum response size to read\n\t\tTimeout:      10,            // Timeout is the time to wait for request in seconds\n\t\tConcurrency:  10,            // Concurrency is the number of concurrent crawling goroutines\n\t\tParallelism:  10,            // Parallelism is the number of urls processing goroutines\n\t\tDelay:        0,             // Delay is the delay between each crawl requests in seconds\n\t\tRateLimit:    150,           // Maximum requests to send per second\n\t\tStrategy:     \"depth-first\", // Visit strategy (depth-first, breadth-first)\n\t\tOnResult: func(result output.Result) { // Callback function to execute for result\n\t\t\tgologger.Info().Msg(result.Request.URL)\n\t\t},\n\t}\n\tcrawlerOptions, err := types.NewCrawlerOptions(options)\n\tif err != nil {\n\t\tgologger.Fatal().Msg(err.Error())\n\t}\n\tdefer crawlerOptions.Close()\n\tcrawler, err := standard.New(crawlerOptions)\n\tif err != nil {\n\t\tgologger.Fatal().Msg(err.Error())\n\t}\n\tdefer crawler.Close()\n\tvar input = \"https://www.hackerone.com\"\n\terr = crawler.Crawl(input)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"Could not crawl %s: %s\", input, err.Error())\n\t}\n}\n```\n\n## Reporting Issues & Feature Requests\n\nTo maintain issue tracking and improve triage efficiency:\n\n**All reports start as [GitHub Discussions](https://github.com/projectdiscovery/katana/discussions)**\n\n- **Bug Reports** → [Start a Q&A Discussion](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)\n- **Feature Requests** → [Start an Ideas Discussion](https://github.com/projectdiscovery/katana/discussions/new?category=ideas)  \n- **Questions** → [Start a Q&A Discussion](https://github.com/projectdiscovery/katana/discussions/new?category=q-a)\n\n**Why Discussions First?**\n- **Community can help** with quick questions and troubleshooting\n- **Better triage** - confirmed bugs/features become tracked issues  \n- **Cleaner issue tracker** - focus on actionable items only\n\nMaintainers will convert discussions to issues when appropriate after proper review.\n\n--------\n\n<div align=\"center\">\n\nkatana is made with ❤️ by the [projectdiscovery](https://projectdiscovery.io) team and distributed under [MIT License](LICENSE.md).\n\n\n<a href=\"https://discord.gg/projectdiscovery\"><img src=\"https://raw.githubusercontent.com/projectdiscovery/nuclei-burp-plugin/main/static/join-discord.png\" width=\"300\" alt=\"Join Discord\"></a>\n\n</div>\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\nDO NOT CREATE AN ISSUE to report a security problem. Instead, please send an email to security@projectdiscovery.io, and we will acknowledge it within 3 working days.\n"
  },
  {
    "path": "cmd/functional-test/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/katana/internal/testutils\"\n)\n\nvar (\n\tdebug           = os.Getenv(\"DEBUG\") == \"true\"\n\tsuccess         = aurora.Green(\"[✓]\").String()\n\tfailed          = aurora.Red(\"[✘]\").String()\n\terrored         = false\n\tdevKatanaBinary = flag.String(\"dev\", \"\", \"Dev Branch Katana Binary\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tif err := runFunctionalTests(); err != nil {\n\t\tlog.Fatalf(\"Could not run functional tests: %s\\n\", err)\n\t}\n\tif errored {\n\t\tos.Exit(1)\n\t}\n}\n\nfunc runFunctionalTests() error {\n\tfor _, testcase := range testutils.TestCases {\n\t\tif err := runIndividualTestCase(testcase); err != nil {\n\t\t\terrored = true\n\t\t\tfmt.Fprintf(os.Stderr, \"%s Test \\\"%s\\\" failed: %s\\n\", failed, testcase.Name, err)\n\t\t} else {\n\t\t\tfmt.Printf(\"%s Test \\\"%s\\\" passed!\\n\", success, testcase.Name)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc runIndividualTestCase(testcase testutils.TestCase) error {\n\targsParts := strings.Fields(testcase.Args)\n\tdevOutput, err := testutils.RunKatanaBinaryAndGetResults(testcase.Target, *devKatanaBinary, debug, argsParts)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not run Katana dev test\")\n\t}\n\tif testcase.CompareFunc != nil {\n\t\treturn testcase.CompareFunc(testcase.Target, devOutput)\n\t}\n\tif !testutils.CompareOutput(devOutput, testcase.Expected) {\n\t\treturn errors.Errorf(\"expected output %s, got %s\", testcase.Expected, devOutput)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/functional-test/run.sh",
    "content": "#!/bin/bash\n\n# reading os type from arguments\nCURRENT_OS=$1\n\nif [ \"${CURRENT_OS}\" == \"windows-latest\" ];then\n    extension=.exe\nfi\n\necho \"::group::Building functional-test binary\"\ngo build -o functional-test$extension\necho \"::endgroup::\"\n\necho \"::group::Building katana binary from current branch\"\ngo build -o katana_dev$extension ../katana\necho \"::endgroup::\"\n\n\necho 'Starting katana functional test'\n./functional-test$extension -dev ./katana_dev$extension\n"
  },
  {
    "path": "cmd/integration-test/filters.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/projectdiscovery/katana/internal/runner\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n)\n\nvar filtersTestcases = map[string]TestCase{\n\t\"match condition\":  &matchConditionIntegrationTest{},\n\t\"filter condition\": &filterConditionIntegrationTest{},\n\t\"unique filter\":    &uniqueFilterIntegrationTest{},\n}\n\ntype matchConditionIntegrationTest struct{}\n\n// Execute executes a test case and returns an error if occurred\n// Execute the docs at ../README.md if the code stops working for integration.\nfunc (h *matchConditionIntegrationTest) Execute() error {\n\tresults, _ := RunKatanaAndGetResults(false,\n\t\t\"-u\", \"scanme.sh\",\n\t\t\"-match-condition\", \"status_code == 200 && contains(body, 'ok')\",\n\t)\n\n\tif len(results) != 1 {\n\t\treturn fmt.Errorf(\"match condition failed\")\n\t}\n\treturn nil\n}\n\ntype filterConditionIntegrationTest struct{}\n\n// Execute executes a test case and returns an error if occurred\nfunc (h *filterConditionIntegrationTest) Execute() error {\n\tresults, _ := RunKatanaAndGetResults(false,\n\t\t\"-u\", \"scanme.sh\",\n\t\t\"-filter-condition\", \"status_code == 200 && contains(body, 'ok')\",\n\t)\n\n\tif len(results) != 0 {\n\t\treturn fmt.Errorf(\"filter condition failed\")\n\t}\n\treturn nil\n}\n\ntype uniqueFilterIntegrationTest struct{}\n\nfunc (h *uniqueFilterIntegrationTest) Execute() error {\n\t// Create a test server that returns 404 for all paths except root\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/\" {\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t_, _ = fmt.Fprint(w, `<html><body>\n\t\t\t\t<a href=\"/page1\">Page 1</a>\n\t\t\t\t<a href=\"/page2\">Page 2</a>\n\t\t\t\t<a href=\"/page3\">Page 3</a>\n\t\t\t\t<a href=\"/page4\">Page 4</a>\n\t\t\t</body></html>`)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\t// Return identical 404 content for all missing pages\n\t\t\t_, _ = fmt.Fprint(w, `<html><body><h1>404 - Page Not Found</h1></body></html>`)\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\toptions := types.DefaultOptions\n\toptions.URLs = []string{server.URL}\n\toptions.MaxDepth = 2\n\toptions.Concurrency = 1\n\toptions.DisableUniqueFilter = true\n\n\tvar fourOhFourCount atomic.Int32\n\toptions.OnResult = func(result output.Result) {\n\t\tif result.Response.StatusCode == http.StatusNotFound {\n\t\t\tfourOhFourCount.Add(1)\n\t\t}\n\t}\n\n\tkatanaRunner, err := runner.New(&options)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not create runner: %v\", err)\n\t}\n\tdefer func() {\n\t\t_ = katanaRunner.Close()\n\t}()\n\n\tif err := katanaRunner.ExecuteCrawling(); err != nil {\n\t\treturn fmt.Errorf(\"could not execute crawling: %v\", err)\n\t}\n\n\tif fourOhFourCount.Load() != 4 {\n\t\treturn fmt.Errorf(\"expected 4 404 responses, got %d\", fourOhFourCount.Load())\n\t}\n\n\treturn nil\n}\n\n// ExtraArgs\nvar ExtraDebugArgs = []string{}\n\nfunc RunKatanaAndGetResults(debug bool, extra ...string) ([]string, error) {\n\tcmd := exec.Command(\"./katana\")\n\textra = append(extra, ExtraDebugArgs...)\n\tcmd.Args = append(cmd.Args, extra...)\n\tcmd.Args = append(cmd.Args, \"-duc\") // disable auto updates\n\tif debug {\n\t\tcmd.Args = append(cmd.Args, \"-debug\")\n\t\tcmd.Stderr = os.Stderr\n\t\tfmt.Println(cmd.String())\n\t} else {\n\t\tcmd.Args = append(cmd.Args, \"-silent\")\n\t}\n\tdata, err := cmd.Output()\n\tif debug {\n\t\tfmt.Println(string(data))\n\t}\n\tif len(data) < 1 && err != nil {\n\t\treturn nil, fmt.Errorf(\"%v: %v\", err.Error(), string(data))\n\t}\n\tvar parts []string\n\titems := strings.Split(string(data), \"\\n\")\n\tfor _, i := range items {\n\t\tif i != \"\" {\n\t\t\tparts = append(parts, i)\n\t\t}\n\t}\n\treturn parts, nil\n}\n"
  },
  {
    "path": "cmd/integration-test/integration-test.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n)\n\ntype TestCase interface {\n\t// Execute executes a test case and returns any errors if occurred\n\tExecute() error\n}\n\nvar (\n\tdebug      = os.Getenv(\"DEBUG\") == \"true\"\n\tcustomTest = os.Getenv(\"TEST\")\n\n\terrored = false\n\tsuccess = aurora.Green(\"[✓]\").String()\n\tfailed  = aurora.Red(\"[✘]\").String()\n\n\ttests = map[string]map[string]TestCase{\n\t\t\"code\":    libraryTestcases,\n\t\t\"filters\": filtersTestcases,\n\t}\n)\n\nfunc main() {\n\tfor name, tests := range tests {\n\t\tfmt.Printf(\"Running test cases for \\\"%s\\\"\\n\", aurora.Blue(name))\n\t\tif customTest != \"\" && !strings.Contains(name, customTest) {\n\t\t\tcontinue // only run tests user asked\n\t\t}\n\t\tfor name, test := range tests {\n\t\t\terr := test.Execute()\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintf(os.Stderr, \"%s Test \\\"%s\\\" failed: %s\\n\", failed, name, err)\n\t\t\t\terrored = true\n\t\t\t} else {\n\t\t\t\tfmt.Printf(\"%s Test \\\"%s\\\" passed!\\n\", success, name)\n\t\t\t}\n\t\t}\n\t}\n\tif errored {\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "cmd/integration-test/library.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com/projectdiscovery/katana/pkg/engine/standard\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/katana/pkg/utils/queue\"\n)\n\nvar libraryTestcases = map[string]TestCase{\n\t\"katana as library\": &goIntegrationTest{},\n}\n\ntype goIntegrationTest struct{}\n\n// Execute executes a test case and returns an error if occurred\n// Execute the docs at ../README.md if the code stops working for integration.\nfunc (h *goIntegrationTest) Execute() error {\n\tvar crawledURLs []string\n\n\toptions := &types.Options{\n\t\tMaxDepth:     1,\n\t\tFieldScope:   \"rdn\",\n\t\tBodyReadSize: math.MaxInt,\n\t\tRateLimit:    150,\n\t\tVerbose:      debug,\n\t\tStrategy:     queue.DepthFirst.String(),\n\t\tOnResult: func(r output.Result) {\n\t\t\tcrawledURLs = append(crawledURLs, r.Request.URL)\n\t\t},\n\t}\n\tcrawlerOptions, err := types.NewCrawlerOptions(options)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := crawlerOptions.Close(); err != nil {\n\t\t\tfmt.Printf(\"Error closing crawler options: %v\\n\", err)\n\t\t}\n\t}()\n\tcrawler, err := standard.New(crawlerOptions)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err := crawler.Close(); err != nil {\n\t\t\tfmt.Printf(\"Error closing crawler: %v\\n\", err)\n\t\t}\n\t}()\n\tvar input = \"https://public-firing-range.appspot.com\"\n\terr = crawler.Crawl(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(crawledURLs) == 0 {\n\t\treturn fmt.Errorf(\"no URLs crawled\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/tools/crawl-maze-score/main.go",
    "content": "package main\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"log\"\n\t\"math\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/projectdiscovery/gologger\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// expectedResults is the list of expected endpoints from security-crawl-maze\n// blueprint directory.\n// https://github.com/google/security-crawl-maze/blob/master/blueprints/utils/resources/expected-results.json\nvar expectedResults = []string{\n\t\"/css/font-face.found\",\n\t\"/headers/content-location.found\",\n\t\"/headers/link.found\",\n\t\"/headers/location.found\",\n\t\"/headers/refresh.found\",\n\t\"/html/doctype.found\",\n\t\"/html/manifest.found\",\n\t\"/html/body/background.found\",\n\t\"/html/body/a/href.found\",\n\t\"/html/body/a/ping.found\",\n\t\"/html/body/audio/src.found\",\n\t\"/html/body/audio/source/src.found\",\n\t\"/html/body/audio/source/srcset1x.found\",\n\t\"/html/body/audio/source/srcset2x.found\",\n\t\"/html/body/applet/archive.found\",\n\t\"/html/body/applet/codebase.found\",\n\t\"/html/body/blockquote/cite.found\",\n\t\"/html/body/embed/src.found\",\n\t\"/html/body/form/action-get.found\",\n\t\"/html/body/form/action-post.found\",\n\t\"/html/body/form/button/formaction.found\",\n\t\"/html/body/frameset/frame/src.found\",\n\t\"/html/body/iframe/src.found\",\n\t\"/html/body/iframe/srcdoc.found\",\n\t\"/html/body/img/dynsrc.found\",\n\t\"/html/body/img/lowsrc.found\",\n\t\"/html/body/img/longdesc.found\",\n\t\"/html/body/img/src-data.found\",\n\t\"/html/body/img/src.found\",\n\t\"/html/body/img/srcset1x.found\",\n\t\"/html/body/img/srcset2x.found\",\n\t\"/html/body/input/src.found\",\n\t\"/html/body/isindex/action.found\",\n\t\"/html/body/map/area/ping.found\",\n\t\"/html/body/object/data.found\",\n\t\"/html/body/object/codebase.found\",\n\t\"/html/body/object/param/value.found\",\n\t\"/html/body/script/src.found\",\n\t\"/html/body/svg/image/xlink.found\",\n\t\"/html/body/svg/script/xlink.found\",\n\t\"/html/body/table/background.found\",\n\t\"/html/body/table/td/background.found\",\n\t\"/html/body/video/src.found\",\n\t\"/html/body/video/track/src.found\",\n\t\"/html/body/video/poster.found\",\n\t\"/html/head/profile.found\",\n\t\"/html/head/base/href.found\",\n\t\"/html/head/comment-conditional.found\",\n\t\"/html/head/import/implementation.found\",\n\t\"/html/head/link/href.found\",\n\t\"/html/head/meta/content-csp.found\",\n\t\"/html/head/meta/content-pinned-websites.found\",\n\t\"/html/head/meta/content-reading-view.found\",\n\t\"/html/head/meta/content-redirect.found\",\n\t\"/html/misc/url/full-url.found\",\n\t\"/html/misc/url/path-relative-url.found\",\n\t\"/html/misc/url/protocol-relative-url.found\",\n\t\"/html/misc/url/root-relative-url.found\",\n\t\"/html/misc/string/dot-dot-slash-prefix.found\",\n\t\"/html/misc/string/dot-slash-prefix.found\",\n\t\"/html/misc/string/url-string.found\",\n\t\"/html/misc/string/string-known-extension.pdf\",\n\t\"/javascript/misc/automatic-post.found\",\n\t\"/javascript/misc/comment.found\",\n\t\"/javascript/misc/string-variable.found\",\n\t\"/javascript/misc/string-concat-variable.found\",\n\t\"/javascript/frameworks/angular/event-handler.found\",\n\t\"/javascript/frameworks/angular/router-outlet.found\",\n\t\"/javascript/frameworks/angularjs/ng-href.found\",\n\t\"/javascript/frameworks/polymer/event-handler.found\",\n\t\"/javascript/frameworks/polymer/polymer-router.found\",\n\t\"/javascript/frameworks/react/route-path.found\",\n\t\"/javascript/frameworks/react/index.html/search.found\",\n\t\"/javascript/interactive/js-delete.found\",\n\t\"/javascript/interactive/js-post.found\",\n\t\"/javascript/interactive/js-post-event-listener.found\",\n\t\"/javascript/interactive/js-put.found\",\n\t\"/javascript/interactive/listener-and-event-attribute-first.found\",\n\t\"/javascript/interactive/listener-and-event-attribute-second.found\",\n\t\"/javascript/interactive/multi-step-request-event-attribute.found\",\n\t\"/test/javascript/interactive/multi-step-request-event-listener-div-dom.found\",\n\t\"/test/javascript/interactive/multi-step-request-event-listener-div.found\",\n\t\"/javascript/interactive/multi-step-request-event-listener-dom.found\",\n\t\"/javascript/interactive/multi-step-request-event-listener.found\",\n\t\"/javascript/interactive/multi-step-request-redefine-event-attribute.found\",\n\t\"/javascript/interactive/multi-step-request-remove-button.found\",\n\t\"/javascript/interactive/multi-step-request-remove-event-listener.found\",\n\t\"/javascript/interactive/two-listeners-first.found\",\n\t\"/javascript/interactive/two-listeners-second.found\",\n\t\"/misc/known-files/robots.txt.found\",\n\t\"/misc/known-files/sitemap.xml.found\",\n}\n\nfunc main() {\n\tif err := process(); err != nil {\n\t\tlog.Fatalf(\"%s\\n\", err)\n\t}\n}\n\nvar urlTestPrefix = \"/test\"\n\nfunc process() error {\n\tif len(os.Args) < 3 {\n\t\tfmt.Printf(\"Usage: crawl-maze-score output.txt output_headless.txt\")\n\t\treturn nil\n\t}\n\tinput := os.Args[1]\n\tinputHeadless := os.Args[2]\n\n\tlinks, err := readFoundLinks(input)\n\tif err != nil {\n\t\treturn err\n\t}\n\tlinksHeadless, err := readFoundLinks(inputHeadless)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tlinksMap := make(map[string]struct{})\n\tlinksHeadlessMap := make(map[string]struct{})\n\tfor _, link := range links {\n\t\tlinksMap[link] = struct{}{}\n\t}\n\tfor _, link := range linksHeadless {\n\t\tlinksHeadlessMap[link] = struct{}{}\n\t}\n\tmatches, matchesHeadless := 0, 0\n\tfor _, expected := range expectedResults {\n\t\texpected = urlTestPrefix + expected\n\n\t\t_, normalOk := linksMap[expected]\n\t\t_, headlessOk := linksHeadlessMap[expected]\n\n\t\tif normalOk {\n\t\t\tmatches++\n\t\t}\n\t\tif headlessOk {\n\t\t\tmatchesHeadless++\n\t\t}\n\t\tfmt.Printf(\"[%s] [%s] %s\\n\", colorizeText(\"standard\", normalOk), colorizeText(\"headless\", headlessOk), expected)\n\t}\n\tfmt.Printf(\"[info] Total links (%d): Standard=>%d Headless=>%d\\n\", len(expectedResults), len(links), len(linksHeadless))\n\tfmt.Printf(\"[info] Total: %d NormalMatches=>%d HeadlessMatches=>%d\\n\", len(expectedResults), matches, matchesHeadless)\n\tfmt.Printf(\"[info] Score: Normal=>%.2f%% Headless=>%.2f%%\\n\", math.Round(float64(matches*100/len(expectedResults))), math.Round(float64(matchesHeadless*100/len(expectedResults))))\n\treturn nil\n}\n\nfunc colorizeText(text string, value bool) string {\n\tif value {\n\t\treturn aurora.Green(text + \":yes\").String()\n\t}\n\treturn aurora.Red(text + \":no\").String()\n}\n\nfunc strippedLink(link string) string {\n\tparsed, err := urlutil.Parse(link)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"failed to parse link while extracting path: %v\", err)\n\t}\n\treturn parsed.Path\n}\n\nfunc readFoundLinks(input string) ([]string, error) {\n\tfile, err := os.Open(input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err := file.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing file: %v\\n\", err)\n\t\t}\n\t}()\n\n\tscanner := bufio.NewScanner(file)\n\tvar links []string\n\tfor scanner.Scan() {\n\t\ttext := scanner.Text()\n\t\tif text == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tif strings.Contains(text, \".found\") {\n\t\t\tlinks = append(links, strippedLink(text))\n\t\t}\n\t}\n\treturn links, nil\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/projectdiscovery/katana\n\ngo 1.25.7\n\nrequire (\n\tgithub.com/BishopFox/jsluice v0.0.0-20240110145140-0ddfab153e06\n\tgithub.com/PuerkitoBio/goquery v1.11.0\n\tgithub.com/adrianbrad/queue v1.3.0\n\tgithub.com/dominikbraun/graph v0.23.0\n\tgithub.com/go-rod/rod v0.116.2\n\tgithub.com/happyhackingspace/dit v0.0.14\n\tgithub.com/hashicorp/golang-lru/v2 v2.0.7\n\tgithub.com/json-iterator/go v1.1.12\n\tgithub.com/lmittmann/tint v1.0.6\n\tgithub.com/logrusorgru/aurora v2.0.3+incompatible\n\tgithub.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3\n\tgithub.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6\n\tgithub.com/mitchellh/mapstructure v1.5.0\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/projectdiscovery/dsl v0.8.5\n\tgithub.com/projectdiscovery/fastdialer v0.5.2\n\tgithub.com/projectdiscovery/goflags v0.1.74\n\tgithub.com/projectdiscovery/gologger v1.1.67\n\tgithub.com/projectdiscovery/hmap v0.0.99\n\tgithub.com/projectdiscovery/mapcidr v1.1.97\n\tgithub.com/projectdiscovery/ratelimit v0.0.82\n\tgithub.com/projectdiscovery/retryablehttp-go v1.3.2\n\tgithub.com/projectdiscovery/utils v0.8.0\n\tgithub.com/projectdiscovery/wappalyzergo v0.2.62\n\tgithub.com/remeh/sizedwaitgroup v1.0.0\n\tgithub.com/rs/xid v1.5.0\n\tgithub.com/stoewer/go-strcase v1.3.0\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/valyala/fasttemplate v1.2.2\n\tgo.uber.org/multierr v1.11.0\n\tgolang.org/x/net v0.51.0\n\tgopkg.in/yaml.v3 v3.0.1\n)\n\nrequire (\n\taead.dev/minisign v0.2.0 // indirect\n\tgithub.com/Knetic/govaluate v3.0.0+incompatible // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect\n\tgithub.com/STARRY-S/zip v0.2.3 // indirect\n\tgithub.com/VividCortex/ewma v1.2.0 // indirect\n\tgithub.com/alecthomas/chroma/v2 v2.14.0 // indirect\n\tgithub.com/andybalholm/brotli v1.2.0 // indirect\n\tgithub.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect\n\tgithub.com/bodgit/plumbing v1.3.0 // indirect\n\tgithub.com/bodgit/sevenzip v1.6.1 // indirect\n\tgithub.com/bodgit/windows v1.0.1 // indirect\n\tgithub.com/brianvoe/gofakeit/v7 v7.2.1 // indirect\n\tgithub.com/charmbracelet/glamour v0.8.0 // indirect\n\tgithub.com/charmbracelet/lipgloss v0.13.0 // indirect\n\tgithub.com/charmbracelet/x/ansi v0.3.2 // indirect\n\tgithub.com/cheggaaa/pb/v3 v3.1.4 // indirect\n\tgithub.com/cloudflare/circl v1.6.1 // indirect\n\tgithub.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c // indirect\n\tgithub.com/djherbis/times v1.6.0 // indirect\n\tgithub.com/dlclark/regexp2 v1.11.5 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/fatih/color v1.15.0 // indirect\n\tgithub.com/felixge/fgprof v0.9.5 // indirect\n\tgithub.com/gaissmai/bart v0.26.0 // indirect\n\tgithub.com/google/go-github/v30 v30.1.0 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gosimple/slug v1.15.0 // indirect\n\tgithub.com/gosimple/unidecode v1.0.1 // indirect\n\tgithub.com/hashicorp/go-version v1.8.0 // indirect\n\tgithub.com/hdm/jarm-go v0.0.7 // indirect\n\tgithub.com/iangcarroll/cookiemonster v1.6.0 // indirect\n\tgithub.com/kataras/jwt v0.1.8 // indirect\n\tgithub.com/klauspost/compress v1.18.2 // indirect\n\tgithub.com/klauspost/pgzip v1.2.6 // indirect\n\tgithub.com/kr/pretty v0.3.1 // indirect\n\tgithub.com/logrusorgru/aurora/v4 v4.0.0 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.16 // indirect\n\tgithub.com/mholt/archives v0.1.5 // indirect\n\tgithub.com/mikelolasagasti/xz v1.0.1 // indirect\n\tgithub.com/minio/minlz v1.0.1 // indirect\n\tgithub.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect\n\tgithub.com/muesli/reflow v0.3.0 // indirect\n\tgithub.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect\n\tgithub.com/nwaples/rardecode/v2 v2.2.2 // indirect\n\tgithub.com/pierrec/lz4/v4 v4.1.23 // indirect\n\tgithub.com/projectdiscovery/asnmap v1.1.1 // indirect\n\tgithub.com/projectdiscovery/blackrock v0.0.1 // indirect\n\tgithub.com/projectdiscovery/gostruct v0.0.2 // indirect\n\tgithub.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect\n\tgithub.com/refraction-networking/utls v1.7.1 // indirect\n\tgithub.com/rivo/uniseg v0.4.7 // indirect\n\tgithub.com/rogpeppe/go-internal v1.12.0 // indirect\n\tgithub.com/sashabaranov/go-openai v1.37.0 // indirect\n\tgithub.com/shirou/gopsutil/v3 v3.23.7 // indirect\n\tgithub.com/shoenig/go-m1cpu v0.1.6 // indirect\n\tgithub.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8 // indirect\n\tgithub.com/sorairolake/lzip-go v0.3.8 // indirect\n\tgithub.com/spaolacci/murmur3 v1.1.0 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/tidwall/btree v1.6.0 // indirect\n\tgithub.com/tidwall/buntdb v1.3.0 // indirect\n\tgithub.com/tidwall/gjson v1.18.0 // indirect\n\tgithub.com/tidwall/grect v0.1.4 // indirect\n\tgithub.com/tidwall/match v1.1.1 // indirect\n\tgithub.com/tidwall/pretty v1.2.1 // indirect\n\tgithub.com/tidwall/rtred v0.1.2 // indirect\n\tgithub.com/tidwall/tinyqueue v0.1.1 // indirect\n\tgithub.com/valyala/bytebufferpool v1.0.0 // indirect\n\tgithub.com/vulncheck-oss/go-exploit v1.51.0 // indirect\n\tgithub.com/xdg-go/pbkdf2 v1.0.0 // indirect\n\tgithub.com/ysmood/fetchup v0.2.3 // indirect\n\tgithub.com/ysmood/got v0.40.0 // indirect\n\tgithub.com/yuin/goldmark v1.7.4 // indirect\n\tgithub.com/yuin/goldmark-emoji v1.0.3 // indirect\n\tgithub.com/zcalusic/sysinfo v1.0.2 // indirect\n\tgo4.org v0.0.0-20230225012048-214862532bf5 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/term v0.40.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n)\n\nrequire (\n\tgithub.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect\n\tgithub.com/akrylysov/pogreb v0.10.1 // indirect\n\tgithub.com/andybalholm/cascadia v1.3.3 // indirect\n\tgithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect\n\tgithub.com/aymerick/douceur v0.2.0 // indirect\n\tgithub.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/dimchansky/utfbom v1.1.1 // indirect\n\tgithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect\n\tgithub.com/go-ole/go-ole v1.2.6 // indirect\n\tgithub.com/golang/snappy v0.0.4 // indirect\n\tgithub.com/gorilla/css v1.0.1 // indirect\n\tgithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect\n\tgithub.com/microcosm-cc/bluemonday v1.0.27 // indirect\n\tgithub.com/miekg/dns v1.1.62 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect\n\tgithub.com/projectdiscovery/networkpolicy v0.1.33\n\tgithub.com/projectdiscovery/retryabledns v1.0.112 // indirect\n\tgithub.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect\n\tgithub.com/syndtr/goleveldb v1.0.0 // indirect\n\tgithub.com/tklauser/go-sysconf v0.3.12 // indirect\n\tgithub.com/tklauser/numcpus v0.6.1 // indirect\n\tgithub.com/ulikunitz/xz v0.5.15 // indirect\n\tgithub.com/weppos/publicsuffix-go v0.40.3-0.20250408071509-6074bbe7fd39 // indirect\n\tgithub.com/ysmood/goob v0.4.0 // indirect\n\tgithub.com/ysmood/gson v0.7.3 // indirect\n\tgithub.com/ysmood/leakless v0.9.0 // indirect\n\tgithub.com/yusufpapurcu/wmi v1.2.4 // indirect\n\tgithub.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect\n\tgithub.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 // indirect\n\tgo.etcd.io/bbolt v1.3.7 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect\n\tgolang.org/x/mod v0.32.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/tools v0.41.0 // indirect\n\tgopkg.in/yaml.v2 v2.4.0\n)\n"
  },
  {
    "path": "go.sum",
    "content": "aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk=\naead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=\ncloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=\ncloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=\ncloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=\ncloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=\ncloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=\ncloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=\ncloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=\ncloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=\ncloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=\ncloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=\ncloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=\ncloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=\ncloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=\ncloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BishopFox/jsluice v0.0.0-20240110145140-0ddfab153e06 h1:xa/dJgg1qpWdIyr7tQcTV2TUPgBK/f0TTMLMmD5GqjQ=\ngithub.com/BishopFox/jsluice v0.0.0-20240110145140-0ddfab153e06/go.mod h1:ENDk4KXEVPZTZPygQAEWJK0BlyEWAyQZhxwCMc+o6A0=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=\ngithub.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=\ngithub.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=\ngithub.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub47e7kd2PLZeACxc1LkiiNoDOFRClE=\ngithub.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057/go.mod h1:iLB2pivrPICvLOuROKmlqURtFIEsoJZaMidQfCG1+D4=\ngithub.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8=\ngithub.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4=\ngithub.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=\ngithub.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=\ngithub.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE=\ngithub.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=\ngithub.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=\ngithub.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=\ngithub.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=\ngithub.com/adrianbrad/queue v1.3.0 h1:8FH1N+93HXbqta5+URa1AL+diV7MP3VDXAEnP+DNp48=\ngithub.com/adrianbrad/queue v1.3.0/go.mod h1:wYiPC/3MPbyT45QHLrPR4zcqJWPePubM1oEP/xTwhUs=\ngithub.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=\ngithub.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI=\ngithub.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=\ngithub.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=\ngithub.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=\ngithub.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=\ngithub.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=\ngithub.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=\ngithub.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=\ngithub.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=\ngithub.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=\ngithub.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=\ngithub.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=\ngithub.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=\ngithub.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=\ngithub.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=\ngithub.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=\ngithub.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=\ngithub.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=\ngithub.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY=\ngithub.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=\ngithub.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=\ngithub.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=\ngithub.com/bodgit/sevenzip v1.6.1 h1:kikg2pUMYC9ljU7W9SaqHXhym5HyKm8/M/jd31fYan4=\ngithub.com/bodgit/sevenzip v1.6.1/go.mod h1:GVoYQbEVbOGT8n2pfqCIMRUaRjQ8F9oSqoBEqZh5fQ8=\ngithub.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=\ngithub.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=\ngithub.com/brianvoe/gofakeit/v7 v7.2.1 h1:AGojgaaCdgq4Adzrd2uWdbGNDyX6MWNhHdQBraNfOHI=\ngithub.com/brianvoe/gofakeit/v7 v7.2.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/charmbracelet/glamour v0.8.0 h1:tPrjL3aRcQbn++7t18wOpgLyl8wrOHUEDS7IZ68QtZs=\ngithub.com/charmbracelet/glamour v0.8.0/go.mod h1:ViRgmKkf3u5S7uakt2czJ272WSg2ZenlYEZXT2x7Bjw=\ngithub.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=\ngithub.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=\ngithub.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=\ngithub.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=\ngithub.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=\ngithub.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo=\ngithub.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA=\ngithub.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=\ngithub.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=\ngithub.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=\ngithub.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=\ngithub.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=\ngithub.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=\ngithub.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=\ngithub.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354=\ngithub.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE=\ngithub.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=\ngithub.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=\ngithub.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=\ngithub.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucVPgCo=\ngithub.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=\ngithub.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=\ngithub.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=\ngithub.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=\ngithub.com/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=\ngithub.com/felixge/fgprof v0.9.5/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/gaissmai/bart v0.26.0 h1:xOZ57E9hJLBiQaSyeZa9wgWhGuzfGACgqp4BE77OkO0=\ngithub.com/gaissmai/bart v0.26.0/go.mod h1:GREWQfTLRWz/c5FTOsIw+KkscuFkIV5t8Rp7Nd1Td5c=\ngithub.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=\ngithub.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=\ngithub.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=\ngithub.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=\ngithub.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=\ngithub.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=\ngithub.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=\ngithub.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo=\ngithub.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=\ngithub.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\ngithub.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=\ngithub.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=\ngithub.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4 h1:gD0vax+4I+mAj+jEChEf25Ia07Jq7kYOFO5PPhAxFl4=\ngithub.com/google/pprof v0.0.0-20250423184734-337e5dd93bb4/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=\ngithub.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=\ngithub.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=\ngithub.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=\ngithub.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=\ngithub.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=\ngithub.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=\ngithub.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=\ngithub.com/happyhackingspace/dit v0.0.14 h1:rkIu0HuFqvqr8F2PJgG0F+lx6DbX/tQE1hXKwIF2NQQ=\ngithub.com/happyhackingspace/dit v0.0.14/go.mod h1:+WeAxrX7QYeiDmXLVaDgrqpyfD4O/sHlOL4wtbiIpUQ=\ngithub.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=\ngithub.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=\ngithub.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A=\ngithub.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ=\ngithub.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=\ngithub.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/iangcarroll/cookiemonster v1.6.0 h1:NPFkn/ZZYZgzXhJ1awRnYhZ3fJK3hKWgbctfTW21kew=\ngithub.com/iangcarroll/cookiemonster v1.6.0/go.mod h1:n3MvoAq56NkNyCEyhcYs3ZJMzTc9rL3w7IaITI0apMg=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\ngithub.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=\ngithub.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=\ngithub.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=\ngithub.com/kataras/jwt v0.1.8 h1:u71baOsYD22HWeSOg32tCHbczPjdCk7V4MMeJqTtmGk=\ngithub.com/kataras/jwt v0.1.8/go.mod h1:Q5j2IkcIHnfwy+oNY3TVWuEBJNw0ADgCcXK9CaZwV4o=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=\ngithub.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=\ngithub.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=\ngithub.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=\ngithub.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=\ngithub.com/lmittmann/tint v1.0.6 h1:vkkuDAZXc0EFGNzYjWcV0h7eEX+uujH48f/ifSkJWgc=\ngithub.com/lmittmann/tint v1.0.6/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=\ngithub.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA=\ngithub.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=\ngithub.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=\ngithub.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3 h1:l1rIRmxNhzeQM+qA3D0CsDLo0Hx45q9JmK0BlCjt6Ks=\ngithub.com/lukasbob/srcset v0.0.0-20190730101422-86b742e617f3/go.mod h1:j16TYl5p17+vBMyaL6Nu4ojlOnfX8lc2k2cfmw6m5TQ=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=\ngithub.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=\ngithub.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=\ngithub.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6 h1:bjfMeqxWEJ6IRUvGkiTkSwx0a6UdQJsbirRSoXogteY=\ngithub.com/mfonda/simhash v0.0.0-20151007195837-79f94a1100d6/go.mod h1:WVJJvUw/pIOcwu2O8ZzHEhmigq2jzwRNfJVRMJB7bR8=\ngithub.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=\ngithub.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=\ngithub.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=\ngithub.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=\ngithub.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=\ngithub.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=\ngithub.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=\ngithub.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=\ngithub.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=\ngithub.com/minio/minlz v1.0.1 h1:OUZUzXcib8diiX+JYxyRLIdomyZYzHct6EShOKtQY2A=\ngithub.com/minio/minlz v1.0.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=\ngithub.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc=\ngithub.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=\ngithub.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=\ngithub.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=\ngithub.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=\ngithub.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=\ngithub.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=\ngithub.com/nwaples/rardecode/v2 v2.2.2 h1:/5oL8dzYivRM/tqX9VcTSWfbpwcbwKG1QtSJr3b3KcU=\ngithub.com/nwaples/rardecode/v2 v2.2.2/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=\ngithub.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=\ngithub.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=\ngithub.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=\ngithub.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=\ngithub.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=\ngithub.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=\ngithub.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=\ngithub.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=\ngithub.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU=\ngithub.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=\ngithub.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=\ngithub.com/projectdiscovery/asnmap v1.1.1 h1:ImJiKIaACOT7HPx4Pabb5dksolzaFYsD1kID2iwsDqI=\ngithub.com/projectdiscovery/asnmap v1.1.1/go.mod h1:QT7jt9nQanj+Ucjr9BqGr1Q2veCCKSAVyUzLXfEcQ60=\ngithub.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ=\ngithub.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss=\ngithub.com/projectdiscovery/dsl v0.8.5 h1:f3opg8Jzikwx6VXC+CbgseUmSUqdfCnfGT08Syhp0sw=\ngithub.com/projectdiscovery/dsl v0.8.5/go.mod h1:AuUq18cpLJJ0uAjJZKaLrdyAgDHrnQAjLMZtPEyMoJw=\ngithub.com/projectdiscovery/fastdialer v0.5.2 h1:BrK23yWc0XD57DMLqnF5oM5tBy8xx9brin+zoSo6gCw=\ngithub.com/projectdiscovery/fastdialer v0.5.2/go.mod h1:euoxS1E93LDnl0OnNN0UALedAFF+EehBxyU3z+79l0g=\ngithub.com/projectdiscovery/goflags v0.1.74 h1:n85uTRj5qMosm0PFBfsvOL24I7TdWRcWq/1GynhXS7c=\ngithub.com/projectdiscovery/goflags v0.1.74/go.mod h1:UMc9/7dFz2oln+10tv6cy+7WZKTHf9UGhaNkF95emh4=\ngithub.com/projectdiscovery/gologger v1.1.67 h1:GZU3AjYiJvcwJT5TlfIv+152/TVmaz62Zyn3/wWXlig=\ngithub.com/projectdiscovery/gologger v1.1.67/go.mod h1:35oeQP6wvj58S+o+Km6boED/t786FXQkI0exhFHJbNE=\ngithub.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M=\ngithub.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE=\ngithub.com/projectdiscovery/hmap v0.0.99 h1:XPfLnD3CUrMqVCIdpK9ozD7Xmp3simx3T+2j4WWhHnU=\ngithub.com/projectdiscovery/hmap v0.0.99/go.mod h1:koyUJi83K5G3w35ZLFXOYZIyYJsO+6hQrgDDN1RBrVE=\ngithub.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q=\ngithub.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=\ngithub.com/projectdiscovery/mapcidr v1.1.97 h1:7FkxNNVXp+m1rIu5Nv/2SrF9k4+LwP8QuWs2puwy+2w=\ngithub.com/projectdiscovery/mapcidr v1.1.97/go.mod h1:9dgTJh1SP02gYZdpzMjm6vtYFkEHQHoTyaVNvaeJ7lA=\ngithub.com/projectdiscovery/networkpolicy v0.1.33 h1:bVgp+XpLEsQ7ZEJt3UaUqIwhI01MMdt7F2dfIKFQg/w=\ngithub.com/projectdiscovery/networkpolicy v0.1.33/go.mod h1:YAPddAXUc/lhoU85AFdvgOQKx8Qh8r0vzSjexRWk6Yk=\ngithub.com/projectdiscovery/ratelimit v0.0.82 h1:rtO5SQf5uQFu5zTahTaTcO06OxmG8EIF1qhdFPIyTak=\ngithub.com/projectdiscovery/ratelimit v0.0.82/go.mod h1:z076BrLkBb5yS7uhHNoCTf8X/BvFSGRxwQ8EzEL9afM=\ngithub.com/projectdiscovery/retryabledns v1.0.112 h1:4iCiuo6jMnw/pdOZRzBQrbUOUu5tOeuvGupxVV8RDLw=\ngithub.com/projectdiscovery/retryabledns v1.0.112/go.mod h1:xsJTKbo+KGqd7+88z1naEUFJybLH2yjB/zUyOweA7k0=\ngithub.com/projectdiscovery/retryablehttp-go v1.3.2 h1:Rv2gw/8t3QZz+WIuHUspVBoRrpBWpVOhzh/wLUGYSVM=\ngithub.com/projectdiscovery/retryablehttp-go v1.3.2/go.mod h1:q1EQ+FX9JP5Z0EqLXDf+8b6XdzWmBXIMPowpI6hQ9aU=\ngithub.com/projectdiscovery/utils v0.8.0 h1:8d79OCs5xGDNXdKxMUKMY/lgQSUWJMYB1B2Sx+oiqkQ=\ngithub.com/projectdiscovery/utils v0.8.0/go.mod h1:CU6tjtyTRxBrnNek+GPJplw4IIHcXNZNKO09kWgqTdg=\ngithub.com/projectdiscovery/wappalyzergo v0.2.62 h1:SMZ70bLCj6jHnFgjanuiaQpqUXY6aiEC3YoM0ZSvYes=\ngithub.com/projectdiscovery/wappalyzergo v0.2.62/go.mod h1:8FtSVcmPRZU0g1euBpdSYEBHIvB7Zz9MOb754ZqZmfU=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/refraction-networking/utls v1.7.1 h1:dxg+jla3uocgN8HtX+ccwDr68uCBBO3qLrkZUbqkcw0=\ngithub.com/refraction-networking/utls v1.7.1/go.mod h1:TUhh27RHMGtQvjQq+RyO11P6ZNQNBb3N0v7wsEjKAIQ=\ngithub.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=\ngithub.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=\ngithub.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=\ngithub.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=\ngithub.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=\ngithub.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=\ngithub.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=\ngithub.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=\ngithub.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=\ngithub.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=\ngithub.com/sashabaranov/go-openai v1.37.0 h1:hQQowgYm4OXJ1Z/wTrE+XZaO20BYsL0R3uRPSpfNZkY=\ngithub.com/sashabaranov/go-openai v1.37.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=\ngithub.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=\ngithub.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=\ngithub.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=\ngithub.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=\ngithub.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=\ngithub.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=\ngithub.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=\ngithub.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8 h1:DxgjlvWYsb80WEN2Zv3WqJFAg2DKjUQJO6URGdf1x6Y=\ngithub.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8/go.mod h1:q99oHDsbP0xRwmn7Vmob8gbSMNyvJ83OauXPSuHQuKE=\ngithub.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=\ngithub.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=\ngithub.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=\ngithub.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=\ngithub.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=\ngithub.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=\ngithub.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=\ngithub.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=\ngithub.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg=\ngithub.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=\ngithub.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA=\ngithub.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU=\ngithub.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=\ngithub.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg=\ngithub.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q=\ngithub.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=\ngithub.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=\ngithub.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=\ngithub.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=\ngithub.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=\ngithub.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=\ngithub.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=\ngithub.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=\ngithub.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=\ngithub.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=\ngithub.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=\ngithub.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=\ngithub.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=\ngithub.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=\ngithub.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=\ngithub.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=\ngithub.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=\ngithub.com/vulncheck-oss/go-exploit v1.51.0 h1:HTmJ4Q94tbEDPb35mQZn6qMg4rT+Sw9n+L7g3Pjr+3o=\ngithub.com/vulncheck-oss/go-exploit v1.51.0/go.mod h1:J28w0dLnA6DnCrnBm9Sbt6smX8lvztnnN2wCXy7No6c=\ngithub.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=\ngithub.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db/go.mod h1:aiQaH1XpzIfgrJq3S1iw7w+3EDbRP7mF5fmwUhWyRUs=\ngithub.com/weppos/publicsuffix-go v0.40.3-0.20250408071509-6074bbe7fd39 h1:Bz/zVM/LoGZ9IztGBHrq2zlFQQbEG8dBYnxb4hamIHM=\ngithub.com/weppos/publicsuffix-go v0.40.3-0.20250408071509-6074bbe7fd39/go.mod h1:2oFzEwGYI7lhiqG0YkkcKa6VcpjVinQbWxaPzytDmLA=\ngithub.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=\ngithub.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=\ngithub.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=\ngithub.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=\ngithub.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=\ngithub.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=\ngithub.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=\ngithub.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=\ngithub.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=\ngithub.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18=\ngithub.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg=\ngithub.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk=\ngithub.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q=\ngithub.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg=\ngithub.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY=\ngithub.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM=\ngithub.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=\ngithub.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=\ngithub.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=\ngithub.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg=\ngithub.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=\ngithub.com/yuin/goldmark-emoji v1.0.3 h1:aLRkLHOuBR2czCY4R8olwMjID+tENfhyFDMCRhbIQY4=\ngithub.com/yuin/goldmark-emoji v1.0.3/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=\ngithub.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngithub.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=\ngithub.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=\ngithub.com/zcalusic/sysinfo v1.0.2 h1:nwTTo2a+WQ0NXwo0BGRojOJvJ/5XKvQih+2RrtWqfxc=\ngithub.com/zcalusic/sysinfo v1.0.2/go.mod h1:kluzTYflRWo6/tXVMJPdEjShsbPpsFRyy+p1mBQPC30=\ngithub.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=\ngithub.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30=\ngithub.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=\ngithub.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=\ngithub.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk=\ngithub.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=\ngithub.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ=\ngithub.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 h1:YOQ1vXEwE4Rnj+uQ/3oCuJk5wgVsvUyW+glsndwYuyA=\ngithub.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968/go.mod h1:xIuOvYCZX21S5Z9bK1BMrertTGX/F8hgAPw7ERJRNS0=\ngithub.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8=\ngo.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=\ngo.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=\ngo.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=\ngo.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=\ngo.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=\ngo4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=\ngolang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=\ngolang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=\ngolang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=\ngolang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=\ngolang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=\ngolang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=\ngolang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=\ngolang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=\ngolang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=\ngolang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=\ngolang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=\ngolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=\ngolang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=\ngolang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=\ngolang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=\ngolang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=\ngolang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=\ngolang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=\ngolang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=\ngolang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=\ngolang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=\ngolang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=\ngolang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=\ngolang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=\ngolang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=\ngolang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=\ngolang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=\ngolang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=\ngolang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=\ngolang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngolang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=\ngolang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=\ngolang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=\ngolang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=\ngolang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=\ngolang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=\ngolang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=\ngolang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=\ngolang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=\ngolang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=\ngolang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=\ngolang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=\ngolang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=\ngolang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=\ngoogle.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=\ngoogle.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=\ngoogle.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=\ngoogle.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=\ngoogle.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=\ngoogle.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=\ngoogle.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=\nrsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=\nrsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=\nrsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=\n"
  },
  {
    "path": "integration_tests/run.sh",
    "content": "#!/bin/bash\n\necho \"::group::Build katana\"\nrm integration-test katana 2>/dev/null\ncd ../cmd/katana\ngo build\nmv katana ../../integration_tests/katana\necho \"::endgroup::\"\n\necho \"::group::Build katana integration-test\"\ncd ../integration-test\ngo build\nmv integration-test ../../integration_tests/integration-test\ncd ../../integration_tests\necho \"::endgroup::\"\n\n./integration-test\nif [ $? -eq 0 ]\nthen\n  exit 0\nelse\n  exit 1\nfi\n"
  },
  {
    "path": "internal/runner/banner.go",
    "content": "package runner\n\nimport (\n\t\"github.com/projectdiscovery/gologger\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n)\n\nvar banner = (`\n   __        __                \n  / /_____ _/ /____ ____  ___ _\n /  '_/ _  / __/ _  / _ \\/ _  /\n/_/\\_\\\\_,_/\\__/\\_,_/_//_/\\_,_/\t\t\t\t\t\t\t \n`)\n\nvar version = \"v1.5.0\"\n\n// showBanner is used to show the banner to the user\nfunc showBanner() {\n\tgologger.Print().Msgf(\"%s\\n\", banner)\n\tgologger.Print().Msgf(\"\\t\\tprojectdiscovery.io\\n\\n\")\n}\n\n// GetUpdateCallback returns a callback function that updates katana\nfunc GetUpdateCallback() func() {\n\treturn func() {\n\t\tshowBanner()\n\t\tupdateutils.GetUpdateToolCallback(\"katana\", version)()\n\t}\n}\n"
  },
  {
    "path": "internal/runner/executer.go",
    "content": "package runner\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/remeh/sizedwaitgroup\"\n)\n\n// ExecuteCrawling executes the crawling main loop\nfunc (r *Runner) ExecuteCrawling() error {\n\tif r.crawler == nil {\n\t\treturn errkit.New(\"crawler is not initialized\")\n\t}\n\tinputs := r.parseInputs()\n\tif len(inputs) == 0 {\n\t\treturn errkit.New(\"no input provided for crawling\")\n\t}\n\n\tfor _, input := range inputs {\n\t\t_ = r.state.InFlightUrls.Set(addSchemeIfNotExists(input), struct{}{})\n\t}\n\n\tdefer func() {\n\t\tif err := r.crawler.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing crawler: %v\\n\", err)\n\t\t}\n\t}()\n\n\twg := sizedwaitgroup.New(r.options.Parallelism)\n\tfor _, input := range inputs {\n\t\tif !r.networkpolicy.Validate(input) {\n\t\t\tgologger.Info().Msgf(\"Skipping excluded host %s\", input)\n\t\t\tcontinue\n\t\t}\n\t\twg.Add()\n\t\tinput = addSchemeIfNotExists(input)\n\t\tgo func(input string) {\n\t\t\tdefer wg.Done()\n\n\t\t\tif err := r.crawler.Crawl(input); err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not crawl %s: %s\", input, err)\n\t\t\t}\n\t\t\tr.state.InFlightUrls.Delete(input)\n\t\t}(input)\n\t}\n\twg.Wait()\n\treturn nil\n}\n\n// scheme less urls are skipped and are required for headless mode and other purposes\n// this method adds scheme if given input does not have any\nfunc addSchemeIfNotExists(inputURL string) string {\n\tif strings.HasPrefix(inputURL, urlutil.HTTP) || strings.HasPrefix(inputURL, urlutil.HTTPS) {\n\t\treturn inputURL\n\t}\n\tparsed, err := urlutil.Parse(inputURL)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"input %v is not a valid url got %v\", inputURL, err)\n\t\treturn inputURL\n\t}\n\tif parsed.Port() != \"\" && (parsed.Port() == \"80\" || parsed.Port() == \"8080\") {\n\t\treturn urlutil.HTTP + urlutil.SchemeSeparator + inputURL\n\t} else {\n\t\treturn urlutil.HTTPS + urlutil.SchemeSeparator + inputURL\n\t}\n}\n"
  },
  {
    "path": "internal/runner/healthcheck.go",
    "content": "package runner\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tpermissionutil \"github.com/projectdiscovery/utils/permission\"\n)\n\nfunc DoHealthCheck(options *types.Options, flagSet *goflags.FlagSet) string {\n\t// RW permissions on config file\n\tcfgFilePath, _ := flagSet.GetConfigFilePath()\n\tvar test strings.Builder\n\t_, _ = fmt.Fprintf(&test, \"Version: %s\\n\", version)\n\t_, _ = fmt.Fprintf(&test, \"Operative System: %s\\n\", runtime.GOOS)\n\t_, _ = fmt.Fprintf(&test, \"Architecture: %s\\n\", runtime.GOARCH)\n\t_, _ = fmt.Fprintf(&test, \"Go Version: %s\\n\", runtime.Version())\n\t_, _ = fmt.Fprintf(&test, \"Compiler: %s\\n\", runtime.Compiler)\n\n\tvar testResult string\n\tif permissionutil.IsRoot {\n\t\ttestResult = \"Ok\"\n\t} else {\n\t\ttestResult = \"Ko\"\n\t}\n\t_, _ = fmt.Fprintf(&test, \"root: %s\\n\", testResult)\n\n\tok, err := fileutil.IsReadable(cfgFilePath)\n\tif ok {\n\t\ttestResult = \"Ok\"\n\t} else {\n\t\ttestResult = \"Ko\"\n\t}\n\tif err != nil {\n\t\ttestResult += fmt.Sprintf(\" (%s)\", err)\n\t}\n\t_, _ = fmt.Fprintf(&test, \"Config file \\\"%s\\\" Read => %s\\n\", cfgFilePath, testResult)\n\tok, err = fileutil.IsWriteable(cfgFilePath)\n\tif ok {\n\t\ttestResult = \"Ok\"\n\t} else {\n\t\ttestResult = \"Ko\"\n\t}\n\tif err != nil {\n\t\ttestResult += fmt.Sprintf(\" (%s)\", err)\n\t}\n\tfmt.Fprintf(&test, \"Config file \\\"%s\\\" Write => %s\\n\", cfgFilePath, testResult)\n\tc4, err := net.Dial(\"tcp4\", \"scanme.sh:80\")\n\tif err == nil && c4 != nil {\n\t\t_ = c4.Close()\n\t}\n\ttestResult = \"Ok\"\n\tif err != nil {\n\t\ttestResult = fmt.Sprintf(\"Ko (%s)\", err)\n\t}\n\tfmt.Fprintf(&test, \"TCP IPv4 connectivity to scanme.sh:80 => %s\\n\", testResult)\n\tc6, err := net.Dial(\"tcp6\", \"scanme.sh:80\")\n\tif err == nil && c6 != nil {\n\t\t_ = c6.Close()\n\t}\n\ttestResult = \"Ok\"\n\tif err != nil {\n\t\ttestResult = fmt.Sprintf(\"Ko (%s)\", err)\n\t}\n\t_, _ = fmt.Fprintf(&test, \"TCP IPv6 connectivity to scanme.sh:80 => %s\\n\", testResult)\n\tu4, err := net.Dial(\"udp4\", \"scanme.sh:53\")\n\tif err == nil && u4 != nil {\n\t\t_ = u4.Close()\n\t}\n\ttestResult = \"Ok\"\n\tif err != nil {\n\t\ttestResult = fmt.Sprintf(\"Ko (%s)\", err)\n\t}\n\t_, _ = fmt.Fprintf(&test, \"UDP IPv4 connectivity to scanme.sh:80 => %s\\n\", testResult)\n\tu6, err := net.Dial(\"udp6\", \"scanme.sh:80\")\n\tif err == nil && u6 != nil {\n\t\t_ = u6.Close()\n\t}\n\ttestResult = \"Ok\"\n\tif err != nil {\n\t\ttestResult = fmt.Sprintf(\"Ko (%s)\", err)\n\t}\n\t_, _ = fmt.Fprintf(&test, \"UDP IPv6 connectivity to scanme.sh:80 => %s\\n\", testResult)\n\n\t// attempt to identify if chome is installed locally\n\tif chromePath, err := exec.LookPath(\"chrome\"); err == nil {\n\t\t_, _ = fmt.Fprintf(&test, \"Potential chrome binary path (linux/osx) => %s\\n\", chromePath)\n\t}\n\tif chromePath, err := exec.LookPath(\"chrome.exe\"); err == nil {\n\t\t_, _ = fmt.Fprintf(&test, \"Potential chrome.exe binary path (windows) => %s\\n\", chromePath)\n\t}\n\n\treturn test.String()\n}\n"
  },
  {
    "path": "internal/runner/options.go",
    "content": "package runner\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/formatter\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\t\"gopkg.in/yaml.v3\"\n)\n\n// validateOptions validates the provided options for crawler\nfunc validateOptions(options *types.Options) error {\n\tif options.MaxDepth <= 0 && options.CrawlDuration.Seconds() <= 0 {\n\t\treturn errkit.New(\"either max-depth or crawl-duration must be specified\")\n\t}\n\tif len(options.URLs) == 0 && !fileutil.HasStdin() {\n\t\treturn errkit.New(\"no inputs specified for crawler\")\n\t}\n\n\t// Disabling automatic form fill (-aff) for headless navigation due to incorrect implementation.\n\t// Form filling should be handled via headless actions within the page context\n\tif options.HeadlessHybrid && options.AutomaticFormFill {\n\t\toptions.AutomaticFormFill = false\n\t\tgologger.Info().Msgf(\"Automatic form fill (-aff) has been disabled for headless navigation.\")\n\t}\n\n\t// Disallow ambiguous engine selection\n\tif options.Headless && options.HeadlessHybrid {\n\t\treturn errkit.New(\"flags -hl (headless) and -hh (hybrid) are mutually exclusive\")\n\t}\n\t\n\tif (options.HeadlessOptionalArguments != nil || options.HeadlessNoSandbox || options.SystemChromePath != \"\") &&\n\t\t!options.Headless && !options.HeadlessHybrid {\n\t\treturn errkit.New(\"headless (-hl) or hybrid (-hh) mode is required if -ho, -nos or -scp are set\")\n\t}\n\tif (options.HeadlessOptionalArguments != nil || options.HeadlessNoSandbox || options.SystemChromePath != \"\") && !options.Headless && !options.HeadlessHybrid {\n\t\treturn errkit.New(\"headless mode (-hl) is required if -ho, -nos or -scp are set\")\n\t}\n\tif options.SystemChromePath != \"\" {\n\t\tif !fileutil.FileExists(options.SystemChromePath) {\n\t\t\treturn errkit.New(\"specified system chrome binary does not exist\")\n\t\t}\n\t}\n\tif options.StoreResponseDir != \"\" && !options.StoreResponse {\n\t\tgologger.Debug().Msgf(\"store response directory specified, enabling \\\"sr\\\" flag automatically\\n\")\n\t\toptions.StoreResponse = true\n\t}\n\tfor _, mr := range options.OutputMatchRegex {\n\t\tcr, err := regexp.Compile(mr)\n\t\tif err != nil {\n\t\t\treturn errkit.Wrap(err, \"Invalid value for match regex option\")\n\t\t}\n\t\toptions.MatchRegex = append(options.MatchRegex, cr)\n\t}\n\tfor _, fr := range options.OutputFilterRegex {\n\t\tcr, err := regexp.Compile(fr)\n\t\tif err != nil {\n\t\t\treturn errkit.Wrap(err, \"Invalid value for filter regex option\")\n\t\t}\n\t\toptions.FilterRegex = append(options.FilterRegex, cr)\n\t}\n\tif options.KnownFiles != \"\" && options.MaxDepth < 3 {\n\t\tgologger.Info().Msgf(\"Depth automatically set to 3 to accommodate the `--known-files` option (originally set to %d).\", options.MaxDepth)\n\t\toptions.MaxDepth = 3\n\t}\n\tgologger.DefaultLogger.SetFormatter(formatter.NewCLI(options.NoColors))\n\treturn nil\n}\n\n// readCustomFormConfig reads custom form fill config\nfunc readCustomFormConfig(formConfig string) error {\n\tfile, err := os.Open(formConfig)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not read form config\")\n\t}\n\tdefer func() {\n\t\tif err := file.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing file: %v\\n\", err)\n\t\t}\n\t}()\n\n\tvar data utils.FormFillData\n\tif err := yaml.NewDecoder(file).Decode(&data); err != nil {\n\t\treturn errkit.Wrap(err, \"could not decode form config\")\n\t}\n\tutils.FormData = data\n\treturn nil\n}\n\n// parseInputs parses the inputs returning a slice of URLs\nfunc (r *Runner) parseInputs() []string {\n\tvalues := make(map[string]struct{})\n\tfor _, url := range r.options.URLs {\n\t\tif url == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tvalue := normalizeInput(url)\n\t\tif _, ok := values[value]; !ok {\n\t\t\tvalues[value] = struct{}{}\n\t\t}\n\t}\n\tif r.stdin {\n\t\tscanner := bufio.NewScanner(os.Stdin)\n\t\tfor scanner.Scan() {\n\t\t\tvalue := normalizeInput(scanner.Text())\n\t\t\tif _, ok := values[value]; !ok {\n\t\t\t\tvalues[value] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\tfinal := make([]string, 0, len(values))\n\tfor k := range values {\n\t\tfinal = append(final, k)\n\t}\n\treturn final\n}\n\nfunc normalizeInput(value string) string {\n\treturn strings.TrimSpace(value)\n}\n\nfunc initExampleFormFillConfig() error {\n\thomedir, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not get home directory\")\n\t}\n\tdefaultConfig := filepath.Join(homedir, \".config\", \"katana\", \"form-config.yaml\")\n\n\tif fileutil.FileExists(defaultConfig) {\n\t\treturn readCustomFormConfig(defaultConfig)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(defaultConfig), 0775); err != nil {\n\t\treturn err\n\t}\n\texampleConfig, err := os.Create(defaultConfig)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"could not get home directory\")\n\t}\n\tdefer func() {\n\t\tif err := exampleConfig.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing example config: %v\\n\", err)\n\t\t}\n\t}()\n\n\terr = yaml.NewEncoder(exampleConfig).Encode(utils.DefaultFormFillData)\n\treturn err\n}\n"
  },
  {
    "path": "internal/runner/runner.go",
    "content": "package runner\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/hybrid\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/standard\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/mapcidr\"\n\t\"github.com/projectdiscovery/mapcidr/asn\"\n\t\"github.com/projectdiscovery/networkpolicy\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tiputil \"github.com/projectdiscovery/utils/ip\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tupdateutils \"github.com/projectdiscovery/utils/update\"\n\t\"go.uber.org/multierr\"\n)\n\n// Runner creates the required resources for crawling\n// and executes the crawl process.\ntype Runner struct {\n\tcrawlerOptions *types.CrawlerOptions\n\tstdin          bool\n\tcrawler        engine.Engine\n\toptions        *types.Options\n\tstate          *RunnerState\n\tnetworkpolicy  *networkpolicy.NetworkPolicy\n}\n\ntype RunnerState struct {\n\tInFlightUrls *mapsutil.SyncLockMap[string, struct{}]\n}\n\n// New returns a new crawl runner structure\nfunc New(options *types.Options) (*Runner, error) {\n\t// create the resume configuration structure\n\tif options.ShouldResume() {\n\t\tgologger.Info().Msg(\"Resuming from save checkpoint\")\n\n\t\tfile, err := os.ReadFile(options.Resume)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\trunnerState := &RunnerState{}\n\t\terr = json.Unmarshal(file, runnerState)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\toptions.URLs = mapsutil.GetKeys(runnerState.InFlightUrls.GetAll())\n\t}\n\toptions.ConfigureOutput()\n\tshowBanner()\n\n\tif options.Version {\n\t\tgologger.Info().Msgf(\"Current version: %s\", version)\n\t\treturn nil, nil\n\t}\n\n\tif !options.DisableUpdateCheck {\n\t\tlatestVersion, err := updateutils.GetToolVersionCallback(\"katana\", version)()\n\t\tif err != nil {\n\t\t\tif options.Verbose {\n\t\t\t\tgologger.Error().Msgf(\"katana version check failed: %v\", err.Error())\n\t\t\t}\n\t\t} else {\n\t\t\tgologger.Info().Msgf(\"Current katana version %v %v\", version, updateutils.GetVersionDescription(version, latestVersion))\n\t\t}\n\t}\n\n\tif err := initExampleFormFillConfig(); err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not init default config\")\n\t}\n\tif err := validateOptions(options); err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not validate options\")\n\t}\n\tif options.FormConfig != \"\" {\n\t\tif err := readCustomFormConfig(options.FormConfig); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tcrawlerOptions, err := types.NewCrawlerOptions(options)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create crawler options\")\n\t}\n\n\tvar crawler engine.Engine\n\n\tswitch {\n\tcase options.ChromeWSUrl != \"\":\n\t\t// When connecting to existing browser via WebSocket URL,\n\t\t// use hybrid engine regardless of other flags\n\t\t// (ChromeWSUrl takes precedence over -headless flag)\n\t\tcrawler, err = hybrid.New(crawlerOptions)\n\tcase options.Headless:\n\t\tcrawler, err = headless.New(crawlerOptions)\n\tcase options.HeadlessHybrid:\n\t\tcrawler, err = hybrid.New(crawlerOptions)\n\tdefault:\n\t\tcrawler, err = standard.New(crawlerOptions)\n\t}\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create standard crawler\")\n\t}\n\n\tvar npOptions networkpolicy.Options\n\n\tfor _, exclude := range options.Exclude {\n\t\tswitch {\n\t\tcase exclude == \"cdn\":\n\t\t\t//implement cdn check in netoworkpolicy pkg??\n\t\t\tcontinue\n\t\tcase exclude == \"private-ips\":\n\t\t\tnpOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv4Denylist...)\n\t\t\tnpOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv4DenylistRanges...)\n\t\t\tnpOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv6Denylist...)\n\t\t\tnpOptions.DenyList = append(npOptions.DenyList, networkpolicy.DefaultIPv6DenylistRanges...)\n\t\tcase iputil.IsCIDR(exclude):\n\t\t\tnpOptions.DenyList = append(npOptions.DenyList, exclude)\n\t\tcase asn.IsASN(exclude):\n\t\t\t// update this to use networkpolicy pkg once https://github.com/projectdiscovery/networkpolicy/pull/55 is merged\n\t\t\tips := expandASNInputValue(exclude)\n\t\t\tnpOptions.DenyList = append(npOptions.DenyList, ips...)\n\t\tcase iputil.IsPort(exclude):\n\t\t\tport, _ := strconv.Atoi(exclude)\n\t\t\tnpOptions.DenyPortList = append(npOptions.DenyPortList, port)\n\t\tdefault:\n\t\t\tnpOptions.DenyList = append(npOptions.DenyList, exclude)\n\t\t}\n\t}\n\n\tnp, _ := networkpolicy.New(npOptions)\n\trunner := &Runner{\n\t\toptions:        options,\n\t\tstdin:          fileutil.HasStdin(),\n\t\tcrawlerOptions: crawlerOptions,\n\t\tcrawler:        crawler,\n\t\tstate:          &RunnerState{InFlightUrls: mapsutil.NewSyncLockMap[string, struct{}]()},\n\t\tnetworkpolicy:  np,\n\t}\n\n\treturn runner, nil\n}\n\n// Close closes the runner releasing resources\nfunc (r *Runner) Close() error {\n\treturn multierr.Combine(\n\t\tr.crawler.Close(),\n\t\tr.crawlerOptions.Close(),\n\t)\n}\n\nfunc (r *Runner) SaveState(resumeFilename string) error {\n\trunnerState := r.state\n\tdata, _ := json.Marshal(runnerState)\n\treturn os.WriteFile(resumeFilename, data, os.ModePerm)\n}\n\nfunc expandCIDRInputValue(value string) []string {\n\tvar ips []string\n\tipsCh, _ := mapcidr.IPAddressesAsStream(value)\n\tfor ip := range ipsCh {\n\t\tips = append(ips, ip)\n\t}\n\treturn ips\n}\n\nfunc expandASNInputValue(value string) []string {\n\tvar ips []string\n\tcidrs, _ := asn.GetCIDRsForASNNum(value)\n\tfor _, cidr := range cidrs {\n\t\tips = append(ips, expandCIDRInputValue(cidr.String())...)\n\t}\n\treturn ips\n}\n"
  },
  {
    "path": "internal/testutils/helper.go",
    "content": "package testutils\n\nfunc CompareOutput(input, expected []string) bool {\n\tif len(input) != len(expected) {\n\t\treturn false\n\t}\n\tfor i, v := range input {\n\t\tif v != expected[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "internal/testutils/integration.go",
    "content": "package testutils\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc RunKatanaBinaryAndGetResults(target string, katanaBinary string, debug bool, args []string) ([]string, error) {\n\tcmd := exec.Command(\"bash\", \"-c\")\n\tcmdLine := fmt.Sprintf(`echo %s | %s `, target, katanaBinary)\n\tcmdLine += strings.Join(args, \" \")\n\n\tcmd.Args = append(cmd.Args, cmdLine)\n\tdata, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparts := []string{}\n\titems := strings.Split(string(data), \"\\n\")\n\tfor _, i := range items {\n\t\tif i != \"\" {\n\t\t\tparts = append(parts, i)\n\t\t}\n\t}\n\treturn parts, nil\n}\n"
  },
  {
    "path": "internal/testutils/testutils.go",
    "content": "package testutils\n\nimport (\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\ntype TestCase struct {\n\tName        string\n\tTarget      string\n\tArgs        string\n\tExpected    []string\n\tCompareFunc func(target string, got []string) error\n}\n\nvar TestCases = []TestCase{\n\t{\n\t\tName:     \"Headless Browser Without Incognito\",\n\t\tTarget:   \"https://www.hackerone.com/\",\n\t\tExpected: nil,\n\t\tArgs:     \"-headless -no-incognito -depth 2 -silent -no-sandbox\",\n\t\tCompareFunc: func(target string, got []string) error {\n\t\t\tfor _, res := range got {\n\t\t\t\tif strings.Contains(res, target) {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn errkit.Newf(\"expected %v target in output, but got %v \", target, strings.Join(got, \"\\n\"))\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "pkg/engine/common/base.go",
    "content": "package common\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/go-rod/rod\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/parser/files\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/projectdiscovery/katana/pkg/utils/queue\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\thttputil \"github.com/projectdiscovery/utils/http\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/remeh/sizedwaitgroup\"\n)\n\n// Shared represents the shared state and configuration used across all crawl sessions.\n// It maintains common resources like HTTP headers, cookie jars, known files database,\n// and crawler options that are reused for efficiency across multiple crawl operations.\ntype Shared struct {\n\tHeaders    map[string]string\n\tKnownFiles *files.KnownFiles\n\tOptions    *types.CrawlerOptions\n\tJar        *httputil.CookieJar\n\tPathTrie   *utils.PathTrie\n}\n\n// NewShared creates a new Shared instance with the provided crawler options.\n// It initializes the HTTP headers, known files database (if configured), and an empty cookie jar.\n// Returns an error if the HTTP client or cookie jar creation fails.\nfunc NewShared(options *types.CrawlerOptions) (*Shared, error) {\n\tshared := &Shared{\n\t\tHeaders: options.Options.ParseCustomHeaders(),\n\t\tOptions: options,\n\t}\n\tif options.Options.KnownFiles != \"\" {\n\t\thttpclient, _, err := BuildHttpClient(options.Dialer, options.Options, nil)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"could not create http client\")\n\t\t}\n\t\tshared.KnownFiles = files.New(httpclient, options.Options.KnownFiles)\n\t}\n\n\t// create an empty cookie jar, this is used to store cookies during the crawl\n\tjar, err := httputil.NewCookieJar()\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create cookie jar\")\n\t}\n\tshared.Jar = jar\n\n\tif options.Options.FilterSimilar {\n\t\tshared.PathTrie = utils.NewPathTrie(options.Options.FilterSimilarThreshold)\n\t}\n\n\treturn shared, nil\n}\n\n// Enqueue adds one or more navigation requests to the crawl queue after applying\n// validation checks. The method performs the following checks in order:\n//  1. URL format validation\n//  2. Query parameter handling (if IgnoreQueryParams is enabled)\n//  3. Depth filtering - skips URLs exceeding MaxDepth before uniqueness check\n//     to prevent caching URLs that would be rejected, allowing them to be\n//     processed if discovered later at valid depths via different paths\n//  4. Uniqueness filtering - prevents duplicate URL crawling\n//  5. Cycle detection - identifies URLs stuck in redirect loops\n//  6. Scope validation - ensures URLs belong to the allowed crawl scope\n//\n// For in-scope URLs, the method also handles path climbing when enabled,\n// extracting and enqueuing parent directory paths.\n// Out-of-scope URLs are sent to output if DisplayOutScope is enabled.\nfunc (s *Shared) Enqueue(queue *queue.Queue, navigationRequests ...*navigation.Request) {\n\tfor _, nr := range navigationRequests {\n\t\tif nr.URL == \"\" || !utils.IsURL(nr.URL) {\n\t\t\tif s.Options.Options.OnSkipURL != nil {\n\t\t\t\ts.Options.Options.OnSkipURL(nr.URL)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\treqUrl := nr.RequestURL()\n\t\tif s.Options.Options.IgnoreQueryParams {\n\t\t\treqUrl = utils.ReplaceAllQueryParam(reqUrl, \"\")\n\t\t}\n\t\tif s.Options.Options.FilterSimilar {\n\t\t\treqUrl = utils.FingerprintURL(reqUrl, s.PathTrie)\n\t\t}\n\n\t\t// Skip adding to the crawl queue when the maximum depth is exceeded.\n\t\t// Must be done before checking uniqueness to avoid caching item that will be skipped\n\t\t// to handle them if faced on lower depth via another path.\n\t\tif nr.Depth > s.Options.Options.MaxDepth {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Ignore blank URL items and only work on unique items\n\t\tif !s.Options.UniqueFilter.UniqueURL(reqUrl) && len(nr.CustomFields) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\t// - URLs stuck in a loop\n\t\tif s.Options.UniqueFilter.IsCycle(nr.RequestURL()) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// skip crawling if the endpoint is not in scope\n\t\tinScope := s.ValidateScope(nr.URL, nr.RootHostname)\n\t\tif !inScope {\n\t\t\t// if the user requested anyway out of scope items\n\t\t\t// they are sent to output without visiting\n\t\t\tif s.Options.Options.DisplayOutScope {\n\t\t\t\ts.Output(nr, nil, ErrOutOfScope)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tqueue.Push(nr, nr.Depth)\n\n\t\tif s.Options.Options.PathClimb {\n\t\t\textractedParentURLs := utils.ExtractParentPaths(nr.URL)\n\t\t\tfor _, extractedParentURL := range extractedParentURLs {\n\t\t\t\tif !utils.IsURL(extractedParentURL) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tcheckURL := extractedParentURL\n\t\t\t\tif s.Options.Options.FilterSimilar {\n\t\t\t\t\tcheckURL = utils.FingerprintURL(checkURL, s.PathTrie)\n\t\t\t\t}\n\t\t\t\tif !s.Options.UniqueFilter.UniqueURL(checkURL) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif !s.ValidateScope(extractedParentURL, nr.RootHostname) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tparentDepth := nr.Depth\n\t\t\t\tif parentDepth > 0 {\n\t\t\t\t\tparentDepth--\n\t\t\t\t}\n\n\t\t\t\tparentReq := &navigation.Request{\n\t\t\t\t\tMethod:       nr.Method,\n\t\t\t\t\tURL:          extractedParentURL,\n\t\t\t\t\tDepth:        parentDepth,\n\t\t\t\t\tRootHostname: nr.RootHostname,\n\t\t\t\t\tSource:       nr.Source,\n\t\t\t\t\tTag:          \"path-climb\",\n\t\t\t\t}\n\t\t\t\tqueue.Push(parentReq, parentDepth)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ValidateScope checks whether a given URL is within the allowed crawling scope\n// based on the configured scope rules and the root hostname.\n// Returns true if the URL passes scope validation, false otherwise.\nfunc (s *Shared) ValidateScope(URL string, root string) bool {\n\tparsed, err := urlutil.Parse(URL)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"failed to parse url while validating scope: %v\", err)\n\t\treturn false\n\t}\n\tscopeValidated, err := s.Options.ScopeManager.Validate(parsed.URL, root)\n\treturn err == nil && scopeValidated\n}\n\n// Output writes a crawl result to the configured output writer.\n// It creates a Result object containing the navigation request, response (if any),\n// and error information (if any), then writes it to the output writer.\n// If an OnResult callback is configured and output writing succeeds, the callback is invoked.\nfunc (s *Shared) Output(navigationRequest *navigation.Request, navigationResponse *navigation.Response, err error) {\n\tvar errData string\n\tif err != nil {\n\t\terrData = err.Error()\n\t}\n\t// Write the found result to output\n\tresult := &output.Result{\n\t\tTimestamp: time.Now(),\n\t\tRequest:   navigationRequest,\n\t\tResponse:  navigationResponse,\n\t\tError:     errData,\n\t}\n\n\toutputErr := s.Options.OutputWriter.Write(result)\n\n\tif s.Options.Options.OnResult != nil && outputErr == nil {\n\t\ts.Options.Options.OnResult(*result)\n\t}\n}\n\n// CrawlSession represents an active crawling session for a specific target URL.\n// It maintains the session context, cancellation function, parsed URL information,\n// the request queue, and HTTP/browser clients needed for the crawl operation.\ntype CrawlSession struct {\n\tCtx        context.Context\n\tCancelFunc context.CancelFunc\n\tURL        *url.URL\n\tHostname   string\n\tQueue      *queue.Queue\n\tHttpClient *retryablehttp.Client\n\tBrowser    *rod.Browser\n}\n\n// NewCrawlSessionWithURL creates and initializes a new crawl session for the specified URL.\n// It performs the following initialization steps:\n//  1. Creates a context with optional timeout based on CrawlDuration setting\n//  2. Parses the target URL and extracts the hostname\n//  3. Initializes the request queue with the configured strategy\n//  4. Enqueues the initial URL and any known files for the target\n//  5. Sets up the HTTP client with response parsing callbacks\n//\n// Returns the initialized CrawlSession or an error if initialization fails.\nfunc (s *Shared) NewCrawlSessionWithURL(URL string) (*CrawlSession, error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tif s.Options.Options.CrawlDuration.Seconds() > 0 {\n\t\t//nolint\n\t\tctx, cancel = context.WithTimeout(ctx, s.Options.Options.CrawlDuration)\n\t}\n\n\tparsed, err := urlutil.Parse(URL)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, errkit.Wrap(err, \"could not parse root URL\")\n\t}\n\thostname := parsed.Hostname()\n\n\tqueue, err := queue.New(s.Options.Options.Strategy, s.Options.Options.Timeout)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\tqueue.Push(&navigation.Request{Method: http.MethodGet, URL: URL, Depth: 0, SkipValidation: true}, 0)\n\n\tif s.KnownFiles != nil {\n\t\tnavigationRequests, err := s.KnownFiles.Request(URL)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not parse known files for %s: %s\\n\", URL, err)\n\t\t}\n\t\ts.Enqueue(queue, navigationRequests...)\n\t}\n\thttpclient, _, err := BuildHttpClient(s.Options.Dialer, s.Options.Options, func(resp *http.Response, depth int) {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\treader, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))\n\t\tvar technologyKeys []string\n\t\tif s.Options.Wappalyzer != nil {\n\t\t\ttechnologies := s.Options.Wappalyzer.Fingerprint(resp.Header, body)\n\t\t\ttechnologyKeys = mapsutil.GetKeys(technologies)\n\t\t}\n\t\tnavigationResponse := &navigation.Response{\n\t\t\tDepth:         depth + 1,\n\t\t\tRootHostname:  hostname,\n\t\t\tResp:          resp,\n\t\t\tBody:          string(body),\n\t\t\tReader:        reader,\n\t\t\tTechnologies:  technologyKeys,\n\t\t\tStatusCode:    resp.StatusCode,\n\t\t\tHeaders:       utils.FlattenHeaders(resp.Header),\n\t\t\tKnowledgeBase: s.Options.ClassifyPage(string(body)),\n\t\t}\n\t\tnavigationRequests := s.Options.Parser.ParseResponse(navigationResponse)\n\t\ts.Enqueue(queue, navigationRequests...)\n\t})\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, errkit.Wrap(err, \"could not create http client\")\n\t}\n\tcrawlSession := &CrawlSession{\n\t\tCtx:        ctx,\n\t\tCancelFunc: cancel,\n\t\tURL:        parsed.URL,\n\t\tHostname:   hostname,\n\t\tQueue:      queue,\n\t\tHttpClient: httpclient,\n\t}\n\treturn crawlSession, nil\n}\n\n// DoRequestFunc is a function type for executing navigation requests.\n// Implementations should perform the actual HTTP request or browser navigation\n// and return the response or an error. This allows different crawling strategies\n// (standard HTTP vs. headless browser) to provide their own request logic.\ntype DoRequestFunc func(crawlSession *CrawlSession, req *navigation.Request) (*navigation.Response, error)\n\n// Do executes the main crawling loop for the given crawl session.\n// It processes items from the queue concurrently (respecting the Concurrency limit),\n// validates each request (URL format, path filters, scope), applies rate limiting\n// and delays, executes the request using the provided doRequest function, writes\n// results to output, and enqueues any newly discovered URLs from responses.\n//\n// The method returns when the queue is empty or the session context is cancelled\n// (due to timeout or manual cancellation). Returns an error if the context is cancelled.\nfunc (s *Shared) Do(crawlSession *CrawlSession, doRequest DoRequestFunc) error {\n\twg := sizedwaitgroup.New(s.Options.Options.Concurrency)\n\tfor item := range crawlSession.Queue.Pop() {\n\t\tif ctxErr := crawlSession.Ctx.Err(); ctxErr != nil {\n\t\t\treturn ctxErr\n\t\t}\n\n\t\treq, ok := item.(*navigation.Request)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !utils.IsURL(req.URL) {\n\t\t\tif s.Options.Options.OnSkipURL != nil {\n\t\t\t\ts.Options.Options.OnSkipURL(req.URL)\n\t\t\t}\n\t\t\tgologger.Debug().Msgf(\"`%v` not a url. skipping\", req.URL)\n\t\t\tcontinue\n\t\t}\n\n\t\tif !s.Options.ValidatePath(req.URL) {\n\t\t\tgologger.Debug().Msgf(\"`%v` filtered path. skipping\", req.URL)\n\t\t\tcontinue\n\t\t}\n\n\t\tinScope, scopeErr := s.Options.ValidateScope(req.URL, crawlSession.Hostname)\n\t\tif scopeErr != nil {\n\t\t\tgologger.Debug().Msgf(\"Error validating scope for `%v`: %v. skipping\", req.URL, scopeErr)\n\t\t\tcontinue\n\t\t}\n\t\tif !req.SkipValidation && !inScope {\n\t\t\tgologger.Debug().Msgf(\"`%v` not in scope. skipping\", req.URL)\n\t\t\tcontinue\n\t\t}\n\n\t\twg.Add()\n\t\t// gologger.Debug().Msgf(\"Visiting: %v\", req.URL) // not sure if this is needed\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\n\t\t\ts.Options.RateLimit.Take()\n\n\t\t\t// Delay if the user has asked for it\n\t\t\tif s.Options.Options.Delay > 0 {\n\t\t\t\ttime.Sleep(time.Duration(s.Options.Options.Delay) * time.Second)\n\t\t\t}\n\n\t\t\tresp, err := doRequest(crawlSession, req)\n\n\t\t\tif inScope {\n\t\t\t\ts.Output(req, resp, err)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tgologger.Warning().Msgf(\"Could not request seed URL %s: %s\\n\", req.URL, err)\n\t\t\t\toutputError := &output.Error{\n\t\t\t\t\tTimestamp: time.Now(),\n\t\t\t\t\tEndpoint:  req.RequestURL(),\n\t\t\t\t\tSource:    req.Source,\n\t\t\t\t\tError:     err.Error(),\n\t\t\t\t}\n\t\t\t\t_ = s.Options.OutputWriter.WriteErr(outputError)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif resp == nil || resp.Resp == nil || resp.Reader == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif s.Options.Options.DisableRedirects && resp.IsRedirect() {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tnavigationRequests := s.Options.Parser.ParseResponse(resp)\n\t\t\ts.Enqueue(crawlSession.Queue, navigationRequests...)\n\t\t}()\n\t}\n\twg.Wait()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/engine/common/error.go",
    "content": "package common\n\nimport \"errors\"\n\nvar ErrOutOfScope = errors.New(\"out of scope\")\n"
  },
  {
    "path": "pkg/engine/common/http.go",
    "content": "package common\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/fastdialer/fastdialer/ja3/impersonate\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tproxyutil \"github.com/projectdiscovery/utils/proxy\"\n)\n\ntype RedirectCallback func(resp *http.Response, depth int)\n\n// BuildHttpClient builds a http client based on a profile\nfunc BuildHttpClient(dialer *fastdialer.Dialer, options *types.Options, redirectCallback RedirectCallback) (*retryablehttp.Client, *fastdialer.Dialer, error) {\n\t// Single Host\n\tretryablehttpOptions := retryablehttp.DefaultOptionsSingle\n\tretryablehttpOptions.RetryMax = options.Retries\n\ttransport := &http.Transport{\n\t\tDialContext: dialer.Dial,\n\t\tDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\tif options.TlsImpersonate {\n\t\t\t\treturn dialer.DialTLSWithConfigImpersonate(ctx, network, addr, &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS10}, impersonate.Random, nil)\n\t\t\t}\n\t\t\treturn dialer.DialTLS(ctx, network, addr)\n\t\t},\n\t\tMaxIdleConns:        100,\n\t\tMaxIdleConnsPerHost: 10,\n\t\tMaxConnsPerHost:     100,\n\t\tTLSClientConfig: &tls.Config{\n\t\t\tRenegotiation:      tls.RenegotiateOnceAsClient,\n\t\t\tInsecureSkipVerify: true,\n\t\t},\n\t\tDisableKeepAlives: false,\n\t}\n\n\t// Attempts to overwrite the dial function with the socks proxied version\n\tif proxyURL, err := url.Parse(options.Proxy); options.Proxy != \"\" && err == nil {\n\t\tif ok, err := proxyutil.IsBurp(options.Proxy); err == nil && ok {\n\t\t\ttransport.TLSClientConfig.MaxVersion = tls.VersionTLS12\n\t\t}\n\t\ttransport.Proxy = http.ProxyURL(proxyURL)\n\t}\n\n\tclient := retryablehttp.NewWithHTTPClient(&http.Client{\n\t\tTransport: transport,\n\t\tTimeout:   time.Duration(options.Timeout) * time.Second,\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\tif options.DisableRedirects {\n\t\t\t\treturn http.ErrUseLastResponse\n\t\t\t}\n\t\t\tif len(via) == 10 {\n\t\t\t\treturn errkit.New(\"stopped after 10 redirects\")\n\t\t\t}\n\t\t\tdepth, ok := req.Context().Value(navigation.Depth{}).(int)\n\t\t\tif !ok {\n\t\t\t\tdepth = 2\n\t\t\t}\n\t\t\tif redirectCallback != nil {\n\t\t\t\tredirectCallback(req.Response, depth)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}, retryablehttpOptions)\n\tclient.CheckRetry = retryablehttp.HostSprayRetryPolicy()\n\treturn client, dialer, nil\n}\n"
  },
  {
    "path": "pkg/engine/engine.go",
    "content": "package engine\n\ntype Engine interface {\n\tCrawl(string) error\n\tClose() error\n}\n"
  },
  {
    "path": "pkg/engine/headless/TODOS.md",
    "content": "# 🗺️ Headless-Crawler Road-Map  \n---\n\n## 🥇  Core Improvements (High-impact / first passes)\n\n- [ ] After clicking on elements there isn't enough wait time to reflect SPA navigation\n\n- [ ] **Replace exact DOM hash with perceptual fingerprint**  \n  - Stage 1: keep current SHA-256 on stripped DOM for cheap exact-match.  \n  - Stage 2: compute 64-bit SimHash/MinHash over 3–4-word shingles of the stripped DOM; treat pages equal when Hamming distance ≤ 3 bits.  \n  - Stage 3 (optional): if SimHash inconclusive, take a low-res screenshot and compare pHash/dHash.  \n  - Store `ExactHash` & `FuzzyHash`; update graph comparison logic.\n\n- [ ] **Robust “page ready” detector**  \n  - Inject `MutationObserver` + `requestIdleCallback`.  \n  - Resolve when:  \n    * no DOM mutation for N ms **and**  \n    * `location.href`/`history.length`/`<title>` stable.  \n  - Wrap as `page.WaitForRouteChange()` and replace `WaitPageLoadHeuristics`.\n\n- [ ] **Lazy-load / infinite-scroll support**  \n  - Loop: `scrollBy(0, viewportHeight*0.9)` until `scrollHeight` stops growing.  \n  - Fallback: IntersectionObserver on a sentinel div.\n\n- [ ] **Capture all secondary resource navigations**  \n  - Enable `FetchEnable` + `Network.*` events.  \n  - Record: XHR/Fetch URLs, WebSocket, EventSource, Service-Worker scripts.  \n  - Feed new, in-scope URLs into the crawl queue (dedup by host/path).\n\n---\n\n## 🥈  Mid-term Enhancements\n\n- [ ] **Dynamic form-filling**  \n  - Generate values based on `type`, `pattern`, `min`, `max`, `maxlength`, `required`.  \n  - Pluggable `ValueProvider` interface for site-specific logic.  \n\n- [ ] **Site adapters / hooks**  \n  - Allow user-supplied Go or JS snippets per hostname (e.g. dismiss cookie wall, auto login, click “load more”).  \n\n- [ ] **Concurrent tab execution**  \n  - Worker pool consuming the action queue.  \n  - Use multiple `rod.Page` instances (shared browser) – make `CrawlGraph` concurrency-safe.\n\n- [ ] **Smart time-out & retry budgets**  \n  - Adaptive timeout: first nav longer, later ones shorter; one automatic reload on stall.  \n\n- [ ] **Viewport variants**  \n  - Crawl again at typical mobile (390×844) & tablet (768×1024) sizes to reveal responsive content.\n\n- [ ] **Memory & process recycling**  \n  - Close background tabs after use.  \n  - If Chrome RSS > threshold, restart browser (persist cookies if needed).\n\n- [ ] **Anti-bot hardening**  \n  - Spoof fonts, canvas & audio contexts.  \n  - Rotate realistic UA strings, languages, `hardwareConcurrency`.  \n  - Optional headful mode via XVFB to enable GPU paths.\n\n---\n\n## 🥉  Nice-to-have / Advanced\n\n- [ ] **Export crawl sessions**  \n  - HAR or WARC output in parallel to existing JSON.\n\n- [ ] **JS coverage tracking**  \n  - `Profiler.startPreciseCoverage` → know which scripts never executed.\n\n- [ ] **Metrics & health**  \n  - Prometheus counters (pages, active tabs, JS errors, nav errors).  \n  - `/debug/pprof` enabled by default.\n\n- [ ] **TLS / proxy flexibility**  \n  - Accept custom CA bundle, client certs, upstream proxy rotation.\n\n- [ ] **Sandboxing & security**  \n  - Run Chrome under seccomp / user-namespaces or separate UID/GID automatically.\n\n- [ ] **Graceful crash recovery**  \n  - Detect `Page.crashed` / `Browser.disconnected`; re-spawn browser, resume queue.\n\n\n----------------------------------\n\nClaude OPUS info below:\n\n\n\n## 🎯 Critical Bug Fixes & Edge Cases\n\n- [ ] **Handle iframe content extraction**\n  - Cross-origin iframe detection and flagging\n  - Same-origin iframe DOM traversal\n  - Nested iframe support (up to N levels)\n\n- [ ] **WebComponent & Shadow DOM support**\n  - Detect custom elements with shadow roots\n  - Traverse open shadow DOMs for form/link discovery\n  - Handle slot-based content projection\n\n- [ ] **Multi-window/tab detection**\n  - Track `window.open()` calls that bypass current hooks\n  - Handle popup windows that close parent\n  - Manage tab focus for proper event firing\n\n## 🔐 Authentication & Session Management\n\n- [ ] **Auth state detection**\n  - Detect login/logout UI patterns\n  - Monitor cookie changes for session tracking\n  - Implement auth health checks between actions\n\n- [ ] **Multi-step auth flows**\n  - OAuth redirect handling\n  - 2FA/MFA detection and waiting\n  - SAML/SSO flow support\n\n- [ ] **Session persistence**\n  - Save/restore cookies between crawls\n  - Handle JWT token refresh\n  - Detect and handle session timeouts\n\n## 🎪 Advanced Interaction Patterns\n\n- [ ] **Complex UI interactions**\n  - Drag & drop detection and execution\n  - File upload with generated test files\n  - Multi-select and combo-box handling\n  - Date/time picker interaction\n\n- [ ] **Keyboard navigation support**\n  - Tab-order based discovery\n  - Keyboard shortcut detection (Ctrl+K, etc.)\n  - Access key enumeration\n\n- [ ] **Touch/mobile gestures**\n  - Swipe detection for mobile views\n  - Long-press context menus\n  - Pinch-to-zoom aware navigation\n\n## 📊 Analytics & Monitoring\n\n- [ ] **Performance metrics**\n  - Page load time tracking\n  - JavaScript execution overhead\n  - Memory usage per page state\n  - Network request waterfalls\n\n- [ ] **Crawl quality metrics**\n  - Code coverage per domain\n  - Unique vs duplicate state ratio\n  - Action success/failure rates\n  - Depth distribution analysis\n\n- [ ] **Error tracking**\n  - JavaScript console error capture\n  - Network error categorization\n  - CSP violation logging\n  - Failed action root cause analysis\n\n## 🧠 Smart Crawling Features\n\n- [ ] **ML-based duplicate detection**\n  - Train model on visual similarity\n  - Semantic HTML structure comparison\n  - Learn site-specific patterns\n\n- [ ] **Priority queue optimization**\n  - High-value path prediction\n  - Anomaly detection for interesting states\n  - Dynamic depth adjustment based on yield\n\n- [ ] **State space reduction**\n  - Identify and prune redundant actions\n  - Detect pagination patterns\n  - Group similar forms (search variations)\n\n## 🛡️ Security & Compliance\n\n- [ ] **CAPTCHA handling**\n  - Detection of common CAPTCHA providers\n  - Integration points for solving services\n  - Graceful degradation strategies\n\n- [ ] **Rate limiting & politeness**\n  - Per-domain request throttling\n  - Respect robots.txt for headless\n  - Adaptive delays based on response times\n\n- [ ] **Privacy compliance**\n  - PII detection in forms\n  - GDPR banner interaction\n  - Data retention policies\n\n## 🔌 Integration Features\n\n- [ ] **API extraction**\n  - GraphQL query/mutation detection\n  - REST endpoint parameter learning\n  - WebSocket message format detection\n\n- [ ] **Export formats**\n  - OpenAPI spec generation from discoveries\n  - Postman collection export\n  - Burp Suite state file compatibility\n\n- [ ] **Workflow recording**\n  - Playwright/Puppeteer script generation\n  - Selenium IDE format export\n  - Custom DSL for replay\n\n## 🚀 Performance Optimizations\n\n- [ ] **Rendering optimizations**\n  - Disable images/fonts for text-only analysis\n  - Viewport-based lazy rendering\n  - CPU throttling for battery saving\n\n- [ ] **Caching layer**\n  - DOM diff caching\n  - Screenshot perceptual hashes\n  - JavaScript execution results\n\n- [ ] **Distributed crawling**\n  - Work queue distribution\n  - State synchronization protocol\n  - Result aggregation pipeline\n\n## 🔧 Developer Experience\n\n- [ ] **Debug tooling**\n  - Live crawl visualization\n  - State graph explorer UI\n  - Action replay debugger\n\n- [ ] **Configuration management**\n  - Per-site config profiles\n  - A/B testing different strategies\n  - Hot-reload of site adapters\n\n- [ ] **Testing infrastructure**\n  - Headless crawler unit tests\n  - Integration tests with test sites\n  - Regression detection suite\n\n\nstate.go is the crawler’s “state-manager”.  \nEverything else in the headless package (browser wrappers, normalizer, graph, diagnostics) either feeds data into it or asks it to restore a known state.  \nTo make the crawler scalable, reliable and de-dupe friendly the file should be responsible for exactly three things:\n\n1. Build a reproducible fingerprint (“state ID”) for the current page.  \n2. Persist the surrounding metadata that we need to replay that state later.  \n3. Provide deterministic, cheapest-first logic to get back to any recorded state.\n\nBelow is a complete design that meets those goals and leaves room for future TODOs.\n\n\n────────────────────────────────────────────────────────────────────────────\n1.   Fingerprint strategy (page → id)\n────────────────────────────────────────────────────────────────────────────\nA. Canonical DOM extraction  \n   • Use the existing domNormalizer (strip scripts, styles, dynamic IDs etc.).  \n   • Remove all transient event-attributes (`onclick`, `onmouseover`, …).  \n   • Collapse whitespace → single space.\n\nB. Two-tier hash  \n   • ExactHash  = SHA-256(strippedDOM).  \n   • FuzzyHash  = SimHash64(4-word shingles of strippedDOM).  \n   • Treat states equal if  \n        - ExactHash matches, or  \n        - Hamming(FuzzyHash, other.FuzzyHash) ≤ 3 bits.  \n   • Persist both; the graph layer deduplicates on (ExactHash || close-enough FuzzyHash).\n\nC. Optional visual fallback  \n   • If comparison is inconclusive (≥ 4 bit distance but DOM len < 1 MiB)  \n     → low-res screenshot, pHash/dHash → same threshold logic.  \n   • Executed lazily to avoid perf hit.\n\nResulting struct:\n\ntype PageState struct {\n    ExactHash  string // always present\n    FuzzyHash  uint64 // present if SimHash computed\n    URL        string\n    Title      string\n    Depth      int\n    StrippedDOM string\n    NavigationAction *Action // edge that produced this state\n    Timestamp  time.Time\n}\n\n–––– Advantages  \n• SimHash makes minor DOM variations (ads, CSRF tokens) resolve to the same state, reducing graph size.  \n• Screenshot hash catches SPA view switches that don’t touch the DOM tree much but look different.\n\n────────────────────────────────────────────────────────────────────────────\n2.   Metadata collection (page → PageState)\n────────────────────────────────────────────────────────────────────────────\nAlgorithm newPageState(page, causingAction):\n\n1. Grab `page.Info()`; bail out if URL is empty or about:blank.  \n2. outerHTML := page.HTML().  \n3. stripped := domNormalizer.Apply(outerHTML).  \n4. Build PageState as above.  \n5. Compute hashes as described.  \n6. Diagnostics hook (save stripped DOM, screenshots, etc.).  \n7. Return the fully populated PageState.\n\nEdge cases handled:  \n• Empty page → custom ErrEmptyPage (already present).  \n• Non-deterministic DOM normalizer failure → bubbled up with context.\n\n────────────────────────────────────────────────────────────────────────────\n3.   Return-to-origin algorithm (current page, targetOriginID) → (pageID, error)\n────────────────────────────────────────────────────────────────────────────\nKeep the existing three-level approach but hard-code their priority and exit conditions.\n\nStep 0   Fast-fail: if currentID == target → done.\n\nStep 1   Element re-use  \n   • If `action.Element` is non-nil, locate by XPath, ensure Visible & Interactable,  \n     *plus* DOM equality check under the canonicalizer to avoid false positives.  \n   • If match, return targetOriginID.\n\nStep 2   Browser history  \n   • page.GetNavigationHistory()  \n   • Walk back until (url == origin.URL && title == origin.Title).  \n   • Limit: max 10 steps to avoid long loops.  \n   • After each back() call wait with WaitForRouteChange() (new detector described below).  \n   • Recompute fingerprint; if equal (exact or fuzzy) → success.\n\nStep 3   Graph shortest path  \n   • crawlerGraph.ShortestPath(currentID, targetID).  \n   • If unreachable, retry from emptyPageHash (fresh tab).  \n   • Execute each Action; after each, WaitForRouteChange().  \n   • After final step verify state (same equality logic as Step 2).  \n   • Failure → ErrNoNavigationPossible.\n\nEnhancements  \n• Cache the computed “distance” between two states; next call can skip graph search.  \n• Record statistics (#navigationBackSuccessByMethod) to tune the priority order.\n\n────────────────────────────────────────────────────────────────────────────\n4.   “Page ready” detector (WaitForRouteChange)\n────────────────────────────────────────────────────────────────────────────\nReplace the brittle WaitPageLoadHeuristics with:\n\nInjected JS once per tab:\n\nconst idle = () => new Promise(res => {\n    const done = () => { obs.disconnect(); res(); };\n    let t;\n    const reset = () => { clearTimeout(t); t = setTimeout(done, 300); };\n    const obs = new MutationObserver(reset);\n    obs.observe(document, {subtree: true, childList: true, attributes: true});\n    reset();\n});\n\nwindow.__katanaReady = () => Promise.all([\n    idle(),\n    new Promise(r => requestIdleCallback(r, {timeout: 5000}))\n]);\n\nGo side:\n\nfunc (p *BrowserPage) WaitForRouteChange() error {\n    ctx, cancel := context.WithTimeout(p.ctx, 15*time.Second)\n    defer cancel()\n    return rod.Try(func() {\n        p.Eval(ctx, `await window.__katanaReady()`)\n    })\n}\n\nDetects route changes, SPA navigations, AJAX content, infinite scroll “settling”, etc.\n\n────────────────────────────────────────────────────────────────────────────\n5.   Extensibility hooks\n────────────────────────────────────────────────────────────────────────────\n• FingerprintStrategy interface so users can plug in custom SimHash/Screenshot logic.  \n• ValueProvider & SiteAdapter interfaces already planned can depend on PageState to decide actions.  \n• Diagnostics sink gets PageState + serialized Action graph for offline visualizer.\n\n────────────────────────────────────────────────────────────────────────────\n6.   Migration plan\n────────────────────────────────────────────────────────────────────────────\n1. Stage 1 (quick): keep old sha256 flow, introduce struct fields & interface but stub SimHash.  \n2. Stage 2: integrate open-source SimHash library, enable fuzzy comparator in graph.  \n3. Stage 3: optional pHash path guarded by feature flag.  \n4. Replace WaitPageLoadHeuristics with WaitForRouteChange().  \n5. Add metrics around navigation-back success.\n\nThis architecture keeps state.go focused, removes hidden coupling, and sets up the crawler for future road-map items (concurrent tabs, adapters, ML dedup, etc.) while remaining incremental enough to merge in small PRs."
  },
  {
    "path": "pkg/engine/headless/browser/browser.go",
    "content": "package browser\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\t\"os/user\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/launcher\"\n\t\"github.com/go-rod/rod/lib/launcher/flags\"\n\t\"github.com/go-rod/rod/lib/proto\"\n\trodutils \"github.com/go-rod/rod/lib/utils\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/browser/cookie\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/browser/stealth\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/js\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/rs/xid\"\n)\n\n// Launcher is a high level controller to launch browsers\n// and do the execution on them.\ntype Launcher struct {\n\tbrowserPool rod.Pool[BrowserPage]\n\n\topts LauncherOptions\n}\n\n// LauncherOptions contains options for the launcher\ntype LauncherOptions struct {\n\tChromiumPath        string\n\tMaxBrowsers         int\n\tPageMaxTimeout      time.Duration\n\tShowBrowser         bool\n\tNoSandbox           bool\n\tProxy               string\n\tSlowMotion          bool\n\tTrace               bool\n\tCookieConsentBypass bool\n\tChromeUser          *user.User // optional chrome user to use\n\n\tScopeValidator  ScopeValidator\n\tRequestCallback func(*output.Result)\n}\n\ntype ScopeValidator func(string) bool\n\n// NewLauncher returns a new launcher instance\nfunc NewLauncher(opts LauncherOptions) (*Launcher, error) {\n\tl := &Launcher{\n\t\topts:        opts,\n\t\tbrowserPool: rod.NewPool[BrowserPage](opts.MaxBrowsers),\n\t}\n\n\treturn l, nil\n}\n\nfunc (l *Launcher) ScopeValidator() ScopeValidator {\n\treturn l.opts.ScopeValidator\n}\n\nfunc (l *Launcher) launchBrowserWithDataDir(userDataDir string) (*rod.Browser, error) {\n\tchromeLauncher := launcher.New().\n\t\tLeakless(true).\n\t\tSet(\"disable-gpu\", \"true\").\n\t\tSet(\"ignore-certificate-errors\", \"true\").\n\t\tSet(\"disable-crash-reporter\", \"true\").\n\t\tSet(\"disable-notifications\", \"true\").\n\t\tSet(\"hide-scrollbars\", \"true\").\n\t\tSet(\"window-size\", fmt.Sprintf(\"%d,%d\", 1080, 1920)).\n\t\tSet(\"mute-audio\", \"true\").\n\t\tSet(\"incognito\", \"true\").\n\t\tDelete(\"use-mock-keychain\").\n\t\tDelete(\"disable-ipc-flooding-protection\").\n\t\tHeadless(true)\n\n\tfor _, flag := range headlessFlags {\n\t\tsplitted := strings.TrimPrefix(flag, \"--\")\n\t\tvalues := strings.Split(splitted, \"=\")\n\t\tif len(values) == 2 {\n\t\t\tchromeLauncher = chromeLauncher.Set(flags.Flag(values[0]), strings.Split(values[1], \",\")...)\n\t\t} else {\n\t\t\tchromeLauncher = chromeLauncher.Set(flags.Flag(splitted), \"true\")\n\t\t}\n\t}\n\n\tif l.opts.Proxy != \"\" {\n\t\tchromeLauncher = chromeLauncher.Proxy(l.opts.Proxy)\n\t}\n\n\tif l.opts.NoSandbox {\n\t\tchromeLauncher = chromeLauncher.NoSandbox(true)\n\t}\n\n\tif l.opts.ShowBrowser {\n\t\tchromeLauncher = chromeLauncher.Headless(false)\n\t}\n\n\tif l.opts.ChromiumPath != \"\" {\n\t\tchromeLauncher = chromeLauncher.Bin(l.opts.ChromiumPath)\n\t}\n\n\tif userDataDir != \"\" {\n\t\tchromeLauncher = chromeLauncher.UserDataDir(userDataDir)\n\t}\n\n\tlauncherURL, err := chromeLauncher.Launch()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbrowser := rod.New().\n\t\tControlURL(launcherURL)\n\tif l.opts.Trace {\n\t\tbrowser = browser.Trace(true)\n\t}\n\n\tif l.opts.SlowMotion {\n\t\tbrowser = browser.SlowMotion(1 * time.Second)\n\t}\n\tif browserErr := browser.Connect(); browserErr != nil {\n\t\treturn nil, browserErr\n\t}\n\n\treturn browser, nil\n}\n\n// Close closes the launcher\nfunc (l *Launcher) Close() {\n\tl.browserPool.Cleanup(func(b *BrowserPage) {\n\t\tb.cancel()\n\t\tb.CloseBrowserPage()\n\t})\n\tclose(l.browserPool)\n}\n\n// BrowserPage is a combination of a browser and a page\ntype BrowserPage struct {\n\t*rod.Page\n\tBrowser     *rod.Browser\n\tcancel      context.CancelFunc\n\tuserDataDir string\n\n\tlauncher *Launcher\n}\n\n// WaitOptions controls how WaitPageLoadHeurisitics determines navigation completion.\n// All durations are conservative defaults and can be tuned later via package-level variables\n// or future setter methods (kept simple here to avoid breaking public API).\ntype WaitOptions struct {\n\tURLPollInterval time.Duration // interval between successive URL polls\n\tURLPollTimeout  time.Duration // how long to keep polling before giving up on URL change\n\tPostChangeWait  time.Duration // small grace period after URL change for late requests\n\tIdleWait        time.Duration // network-idle window when no URL change happened\n\tDOMStableWait   time.Duration // DOM-stable window (used after idle)\n\tMaxTimeout      time.Duration // absolute upper bound for all waiting\n}\n\n// defaultWaitOptions are derived from empirical measurements on modern SPA pages.\nvar defaultWaitOptions = WaitOptions{\n\tURLPollInterval: 100 * time.Millisecond,\n\tURLPollTimeout:  2 * time.Second,\n\tPostChangeWait:  300 * time.Millisecond,\n\tIdleWait:        1 * time.Second,\n\tDOMStableWait:   1 * time.Second,\n\tMaxTimeout:      15 * time.Second,\n}\n\n// WaitPageLoadHeurisitics waits for the page to load using multiple heuristics.\n// Strategy order:\n//  1. Wait for initial load event (covers classic navigation & first paint).\n//  2. Poll for a URL change – the strongest signal on SPAs with client-side routing.\n//  3. If URL changes, wait a short grace period + network-idle window.\n//  4. If URL doesn't change, fall back to network-idle + DOM-stable windows.\n//\n// This keeps fast pages fast while still succeeding on noisy, long-running SPAs.\nfunc (b *BrowserPage) WaitPageLoadHeurisitics() error {\n\topts := defaultWaitOptions\n\n\tchained := b.Timeout(opts.MaxTimeout)\n\n\t// 1. Wait for the basic load event (DOMContentLoaded / load).\n\t_ = chained.WaitLoad()\n\n\t// 2. Capture the current URL so we can detect route changes.\n\turlVal, _ := b.Eval(\"() => window.location.href\")\n\tstartURL := \"\"\n\tif urlVal != nil {\n\t\tstartURL = urlVal.Value.Str()\n\t}\n\n\t// 3. Poll for a different URL for up to URLPollTimeout.\n\turlChanged := false\n\tif startURL != \"\" {\n\t\tpollCount := int(opts.URLPollTimeout / opts.URLPollInterval)\n\t\tfor i := 0; i < pollCount; i++ {\n\t\t\ttime.Sleep(opts.URLPollInterval)\n\t\t\tcur, err := b.Eval(\"() => window.location.href\")\n\t\t\tif err == nil && cur != nil && cur.Value.Str() != startURL {\n\t\t\t\turlChanged = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif urlChanged {\n\t\t// 4a. URL changed – short grace period then network idle & done.\n\t\t_ = chained.WaitIdle(opts.PostChangeWait)\n\t\treturn nil\n\t}\n\n\t// 4b. URL didn't change – fall back to broader heuristics.\n\t_ = chained.WaitIdle(opts.IdleWait)\n\t_ = b.WaitNewStable(opts.DOMStableWait)\n\n\treturn nil\n}\n\n// WaitPageLoadHeuristicsFallback provides the enhanced timeouts for complex navigation\nfunc (b *BrowserPage) WaitPageLoadHeuristicsFallback() error {\n\tchainedTimeout := b.Timeout(20 * time.Second)\n\n\t_ = chainedTimeout.WaitLoad()\n\t_ = chainedTimeout.WaitIdle(4 * time.Second)\n\t_ = b.WaitNewStable(2 * time.Second)\n\n\treturn nil\n}\n\n// WaitStable waits until the page is stable for d duration.\nfunc (p *BrowserPage) WaitNewStable(d time.Duration) error {\n\t// Enforce an upper-bound on how long we will wait for the page to become\n\t// stable. We simply reuse the heuristic window (d) and give the combined\n\t// operation 2× that duration. This guarantees that callers will be\n\t// released after a finite time instead of blocking forever when a page\n\t// keeps a long-lived connection open (analytics beacons, WebSockets, etc.).\n\n\tchained := p.Timeout(2 * d)\n\n\tvar err error\n\tsetErr := sync.Once{}\n\n\trodutils.All(func() {\n\t\te := chained.WaitLoad()\n\t\tsetErr.Do(func() { err = e })\n\t}, func() {\n\t\tchained.WaitRequestIdle(d, nil, []string{}, nil)()\n\t}, func() {\n\t\te := chained.WaitDOMStable(d, 0)\n\t\tsetErr.Do(func() { err = e })\n\t})()\n\n\treturn err\n}\n\nfunc (l *Launcher) createBrowserPageFunc() (*BrowserPage, error) {\n\t// Create unique temp userDataDir for this browser instance\n\tvar tempDir string\n\tshouldCleanup := true\n\n\t// Deferred cleanup function that will be set after tempDir creation\n\tdefer func() {\n\t\tif shouldCleanup && tempDir != \"\" {\n\t\t\t_ = os.RemoveAll(tempDir)\n\t\t}\n\t}()\n\n\tif l.opts.ChromeUser != nil {\n\t\tvar err error\n\t\ttempDir, err = os.MkdirTemp(l.opts.ChromeUser.HomeDir, \"chrome-data-*\")\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create temporary chrome data directory\")\n\t\t}\n\n\t\tuid, err := strconv.Atoi(l.opts.ChromeUser.Uid)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"invalid user ID\")\n\t\t}\n\t\tgid, err := strconv.Atoi(l.opts.ChromeUser.Gid)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"invalid group ID\")\n\t\t}\n\t\tif err := os.Chown(tempDir, uid, gid); err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not change ownership of chrome data directory\")\n\t\t}\n\t} else {\n\t\tvar err error\n\t\ttempDir, err = os.MkdirTemp(\"\", \"katana-chrome-data-*\")\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not create temporary chrome data directory\")\n\t\t}\n\t}\n\n\tbrowser, err := l.launchBrowserWithDataDir(tempDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpage, err := browser.Page(proto.TargetCreateTarget{})\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create new page\")\n\t}\n\n\tsuccessfulPageCreation := false\n\tdefer func() {\n\t\tif !successfulPageCreation {\n\t\t\t_ = page.Close()\n\t\t\t_ = browser.Close()\n\t\t}\n\t}()\n\n\tpage = page.Sleeper(func() rodutils.Sleeper {\n\t\treturn backoffCountSleeper(100*time.Millisecond, 1*time.Second, 3, func(d time.Duration) time.Duration {\n\t\t\treturn d * 1\n\t\t})\n\t})\n\tctx := page.GetContext()\n\tcancelCtx, cancel := context.WithCancel(ctx)\n\tpage = page.Context(cancelCtx)\n\n\tbrowserPage := &BrowserPage{\n\t\tPage:        page,\n\t\tBrowser:     browser,\n\t\tlauncher:    l,\n\t\tcancel:      cancel,\n\t\tuserDataDir: tempDir,\n\t}\n\tif err := browserPage.handlePageDialogBoxes(); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Add stealth evasion JS\n\t_, err = page.EvalOnNewDocument(stealth.JS)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not initialize stealth\")\n\t}\n\terr = js.InitJavascriptEnv(page)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not initialize javascript env\")\n\t}\n\n\t// Success - cancel the deferred cleanup\n\tsuccessfulPageCreation = true\n\tshouldCleanup = false\n\treturn browserPage, nil\n}\n\n// GetPageFromPool returns a page from the pool\nfunc (l *Launcher) GetPageFromPool() (*BrowserPage, error) {\n\tbrowserPage, err := l.browserPool.Get(l.createBrowserPageFunc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// TODO: should we check if the browser is alive because sometimes it\n\t// might die?\n\treturn browserPage, nil\n}\n\n// backoffCountSleeper returns a sleeper that uses backoff strategy but stops after max attempts.\n// It combines the functionality of BackoffSleeper and CountSleeper.\nfunc backoffCountSleeper(initInterval, maxInterval time.Duration, maxAttempts int, algorithm func(time.Duration) time.Duration) rodutils.Sleeper {\n\tbackoff := rodutils.BackoffSleeper(initInterval, maxInterval, algorithm)\n\tcount := rodutils.CountSleeper(maxAttempts)\n\n\treturn rodutils.EachSleepers(backoff, count)\n}\n\nfunc (b *BrowserPage) handlePageDialogBoxes() error {\n\terr := proto.FetchEnable{\n\t\tPatterns: []*proto.FetchRequestPattern{\n\t\t\t{\n\t\t\t\tURLPattern:   \"*\",\n\t\t\t\tRequestStage: proto.FetchRequestStageResponse,\n\t\t\t},\n\t\t},\n\t}.Call(b.Page)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not enable fetch domain\")\n\t}\n\n\tgo b.EachEvent(\n\t\tfunc(e *proto.PageJavascriptDialogOpening) {\n\t\t\t_ = proto.PageHandleJavaScriptDialog{\n\t\t\t\tAccept:     true,\n\t\t\t\tPromptText: xid.New().String(),\n\t\t\t}.Call(b.Page)\n\t\t},\n\n\t\tfunc(e *proto.FetchRequestPaused) {\n\t\t\tif b.launcher.opts.CookieConsentBypass {\n\t\t\t\t// Check if request should be blocked by cookie consent rules\n\t\t\t\tvar originStr string\n\t\t\t\tif origin, ok := e.Request.Headers[\"Origin\"]; ok {\n\t\t\t\t\toriginStr = origin.Str()\n\t\t\t\t}\n\t\t\t\tif cookie.ShouldBlockRequest(e.Request.URL, e.ResourceType, originStr) {\n\t\t\t\t\t_ = proto.FetchFailRequest{\n\t\t\t\t\t\tRequestID:   e.RequestID,\n\t\t\t\t\t\tErrorReason: proto.NetworkErrorReasonBlockedByClient,\n\t\t\t\t\t}.Call(b.Page)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif e.ResponseStatusCode == nil || e.ResponseErrorReason != \"\" || (*e.ResponseStatusCode >= 301 && *e.ResponseStatusCode <= 308) {\n\t\t\t\tif err := fetchContinueRequest(b.Page, e); err != nil {\n\t\t\t\t\tslog.Warn(\"fetchContinueRequest failed\", \"error\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tbody, err := fetchGetResponseBody(b.Page, e)\n\t\t\tif err != nil {\n\t\t\t\t// Continue the request even if we can't get the body\n\t\t\t\tif err := fetchContinueRequest(b.Page, e); err != nil {\n\t\t\t\t\tslog.Warn(\"fetchContinueRequest failed\", \"error\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err := fetchContinueRequest(b.Page, e); err != nil {\n\t\t\t\tslog.Warn(\"fetchContinueRequest failed\", \"error\", err)\n\t\t\t}\n\n\t\t\thttpreq, err := netHTTPRequestFromProto(e.Request)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trawBytesRequest, _ := httputil.DumpRequestOut(httpreq, true)\n\n\t\t\treq := navigation.Request{\n\t\t\t\tMethod:  httpreq.Method,\n\t\t\t\tURL:     httpreq.URL.String(),\n\t\t\t\tBody:    e.Request.PostData,\n\t\t\t\tHeaders: utils.FlattenHeaders(httpreq.Header),\n\t\t\t\tRaw:     string(rawBytesRequest),\n\t\t\t}\n\n\t\t\thttpresp := netHTTPResponseFromProto(e, body)\n\t\t\thttpresp.Request = httpreq\n\n\t\t\trawBytesResponse, _ := httputil.DumpResponse(httpresp, true)\n\n\t\t\tdoc, err := goquery.NewDocumentFromReader(bytes.NewReader(body))\n\t\t\tif err != nil {\n\t\t\t\tslog.Warn(\"could not parse response body\", \"error\", err)\n\t\t\t}\n\t\t\tresp := &navigation.Response{\n\t\t\t\tBody:          string(body),\n\t\t\t\tStatusCode:    httpresp.StatusCode,\n\t\t\t\tHeaders:       utils.FlattenHeaders(httpresp.Header),\n\t\t\t\tRaw:           string(rawBytesResponse),\n\t\t\t\tContentLength: httpresp.ContentLength,\n\t\t\t\tResp:          httpresp,\n\t\t\t\tReader:        doc,\n\t\t\t}\n\t\t\tif b.launcher.opts.RequestCallback != nil {\n\t\t\t\tb.launcher.opts.RequestCallback(&output.Result{\n\t\t\t\t\tTimestamp: time.Now(),\n\t\t\t\t\tRequest:   &req,\n\t\t\t\t\tResponse:  resp,\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t)()\n\treturn nil\n}\n\nfunc fetchContinueRequest(page *rod.Page, e *proto.FetchRequestPaused) error {\n\treturn proto.FetchContinueRequest{\n\t\tRequestID: e.RequestID,\n\t}.Call(page)\n}\n\n// fetchGetResponseBody get request body.\nfunc fetchGetResponseBody(page *rod.Page, e *proto.FetchRequestPaused) ([]byte, error) {\n\tm := proto.FetchGetResponseBody{\n\t\tRequestID: e.RequestID,\n\t}\n\tr, err := m.Call(page)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !r.Base64Encoded {\n\t\treturn []byte(r.Body), nil\n\t}\n\n\tbs, err := base64.StdEncoding.DecodeString(r.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bs, nil\n}\n\nfunc netHTTPRequestFromProto(e *proto.NetworkRequest) (*http.Request, error) {\n\treq, err := http.NewRequest(e.Method, e.URL, nil)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not create new request\")\n\t}\n\tfor k, v := range e.Headers {\n\t\treq.Header.Set(k, v.Str())\n\t}\n\tif e.PostData != \"\" {\n\t\treq.Body = io.NopCloser(strings.NewReader(e.PostData))\n\t\treq.ContentLength = int64(len(e.PostData))\n\t}\n\treturn req, nil\n}\n\nfunc netHTTPResponseFromProto(e *proto.FetchRequestPaused, body []byte) *http.Response {\n\thttpresp := &http.Response{\n\t\tProto:         \"HTTP/1.1\",\n\t\tProtoMajor:    1,\n\t\tProtoMinor:    1,\n\t\tHeader:        make(http.Header),\n\t\tStatusCode:    *e.ResponseStatusCode,\n\t\tStatus:        e.ResponseStatusText,\n\t\tBody:          io.NopCloser(bytes.NewReader(body)),\n\t\tContentLength: int64(len(body)),\n\t}\n\tfor _, header := range e.ResponseHeaders {\n\t\thttpresp.Header.Set(header.Name, header.Value)\n\t}\n\treturn httpresp\n}\n\nfunc (l *Launcher) PutBrowserToPool(browser *BrowserPage) {\n\t// Discard pages that hit a deadline or were cancelled to avoid immediately\n\t// returning a poisoned page that will fail every subsequent call.\n\tif cerr := browser.Page.GetContext().Err(); cerr != nil {\n\t\tbrowser.cancel()\n\t\tbrowser.CloseBrowserPage()\n\t\treturn\n\t}\n\t// If the browser is not connected, close it\n\tif !isBrowserConnected(browser.Browser) {\n\t\tbrowser.cancel()\n\t\tbrowser.CloseBrowserPage()\n\t\treturn\n\t}\n\n\tpages, err := browser.Browser.Pages()\n\tif err != nil {\n\t\tbrowser.cancel()\n\t\tbrowser.CloseBrowserPage()\n\t\treturn\n\t}\n\n\tcurrentPageID := browser.TargetID\n\tfor _, page := range pages {\n\t\tif page.TargetID != currentPageID {\n\t\t\t_ = page.Close()\n\t\t}\n\t}\n\tl.browserPool.Put(browser)\n}\n\nfunc isBrowserConnected(browser *rod.Browser) bool {\n\tgetVersionResult, err := proto.BrowserGetVersion{}.Call(browser)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif getVersionResult == nil || getVersionResult.Product == \"\" {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (b *BrowserPage) CloseBrowserPage() {\n\t_ = b.Close()\n\t_ = b.Browser.Close()\n\tif b.userDataDir != \"\" {\n\t\t_ = os.RemoveAll(b.userDataDir)\n\t}\n}\n\n// taken from playwright\nvar headlessFlags = []string{\n\t\"--disable-field-trial-config\", // https://source.chromium.org/chromium/chromium/src/+/main:testing/variations/README.md\n\t\"--disable-background-networking\",\n\t\"--enable-features=NetworkService,NetworkServiceInProcess\",\n\t\"--disable-background-timer-throttling\",\n\t\"--disable-backgrounding-occluded-windows\",\n\t\"--disable-back-forward-cache\", // Avoids surprises like main request not being intercepted during page.goBack().\n\t\"--disable-breakpad\",\n\t\"--disable-client-side-phishing-detection\",\n\t\"--disable-component-extensions-with-background-pages\",\n\t\"--disable-component-update\", // Avoids unneeded network activity after startup.\n\t\"--no-default-browser-check\",\n\t\"--disable-default-apps\",\n\t\"--disable-dev-shm-usage\",\n\t\"--disable-extensions\",\n\t// AvoidUnnecessaryBeforeUnloadCheckSync - https://github.com/microsoft/playwright/issues/14047\n\t// Translate - https://github.com/microsoft/playwright/issues/16126\n\t// HttpsUpgrades - https://github.com/microsoft/playwright/pull/27605\n\t// PaintHolding - https://github.com/microsoft/playwright/issues/28023\n\t\"--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose,MediaRouter,DialMediaRouteProvider,AcceptCHFrame,AutoExpandDetailsElement,CertificateTransparencyComponentUpdater,AvoidUnnecessaryBeforeUnloadCheckSync,Translate,HttpsUpgrades,PaintHolding\",\n\t\"--allow-pre-commit-input\",\n\t\"--disable-hang-monitor\",\n\t\"--disable-popup-blocking\",\n\t\"--disable-prompt-on-repost\",\n\t\"--disable-renderer-backgrounding\",\n\t\"--force-color-profile=srgb\",\n\t\"--metrics-recording-only\",\n\t\"--no-first-run\",\n\t\"--enable-automation\",\n\t\"--password-store=basic\",\n\t\"--use-mock-keychain\",\n\t// See https://chromium-review.googlesource.com/c/chromium/src/+/2436773\n\t\"--no-service-autorun\",\n\t\"--export-tagged-pdf\",\n\t// https://chromium-review.googlesource.com/c/chromium/src/+/4853540\n\t\"--disable-search-engine-choice-screen\",\n\t// https://issues.chromium.org/41491762\n\t\"--unsafely-disable-devtools-self-xss-warnings\",\n}\n"
  },
  {
    "path": "pkg/engine/headless/browser/cookie/cookie.go",
    "content": "// Package cookie implements cookie consent handling for the\n// crawler. Its a partial port of I-Still-Dont-Care-About-Cookies.\npackage cookie\n\nimport (\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/go-rod/rod/lib/proto\"\n)\n\ntype CookieConsentBlockRequest struct {\n\tID        int       `json:\"id\"`\n\tPriority  int       `json:\"priority\"`\n\tCondition Condition `json:\"condition,omitempty\"`\n}\n\ntype Action struct {\n\tType string `json:\"type\"`\n}\n\ntype Condition struct {\n\tURLFilter                string   `json:\"urlFilter\"`\n\tResourceTypes            []string `json:\"resourceTypes\"`\n\tInitiatorDomains         []string `json:\"initiatorDomains\"`\n\tExcludedInitiatorDomains []string `json:\"excludedInitiatorDomains\"`\n}\n\n//go:embed rules.json\nvar rules []byte\n\nvar cookieConsentBlockRequests []CookieConsentBlockRequest\n\nfunc init() {\n\terr := json.Unmarshal(rules, &cookieConsentBlockRequests)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tsort.SliceStable(cookieConsentBlockRequests, func(i, j int) bool {\n\t\treturn cookieConsentBlockRequests[i].Priority > cookieConsentBlockRequests[j].Priority\n\t})\n}\n\n// ShouldBlockRequest determines if a request should be blocked based on cookie consent rules\nfunc ShouldBlockRequest(url string, resourceType proto.NetworkResourceType, initiatorDomain string) bool {\n\tresourceTypeStr := getResourceType(resourceType)\n\tfor _, rule := range cookieConsentBlockRequests {\n\t\tif matchesRule(rule, url, resourceTypeStr, initiatorDomain) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// matchesRule checks if a request matches a specific cookie consent block rule\nfunc matchesRule(rule CookieConsentBlockRequest, url string, resourceType string, initiatorDomain string) bool {\n\tif !strings.Contains(url, rule.Condition.URLFilter) {\n\t\treturn false\n\t}\n\n\tif len(rule.Condition.ResourceTypes) > 0 {\n\t\tmatched := false\n\t\tfor _, rt := range rule.Condition.ResourceTypes {\n\t\t\tif rt == resourceType {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif len(rule.Condition.InitiatorDomains) > 0 {\n\t\tmatched := false\n\t\tfor _, domain := range rule.Condition.InitiatorDomains {\n\t\t\tif strings.Contains(initiatorDomain, domain) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif len(rule.Condition.ExcludedInitiatorDomains) > 0 {\n\t\tfor _, domain := range rule.Condition.ExcludedInitiatorDomains {\n\t\t\tif strings.Contains(initiatorDomain, domain) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc getResourceType(resourceType proto.NetworkResourceType) string {\n\tswitch resourceType {\n\tcase proto.NetworkResourceTypeStylesheet:\n\t\treturn \"stylesheet\"\n\tcase proto.NetworkResourceTypeScript:\n\t\treturn \"script\"\n\tcase proto.NetworkResourceTypeImage:\n\t\treturn \"image\"\n\tcase proto.NetworkResourceTypeFont:\n\t\treturn \"font\"\n\tcase proto.NetworkResourceTypeXHR:\n\t\treturn \"xmlhttprequest\"\n\tcase proto.NetworkResourceTypePing:\n\t\treturn \"ping\"\n\tcase proto.NetworkResourceTypeCSPViolationReport:\n\t\treturn \"csp_report\"\n\tcase proto.NetworkResourceTypeMedia:\n\t\treturn \"media\"\n\tcase proto.NetworkResourceTypeWebSocket:\n\t\treturn \"websocket\"\n\tcase proto.NetworkResourceTypeOther:\n\t\treturn \"other\"\n\t}\n\treturn string(resourceType)\n}\n"
  },
  {
    "path": "pkg/engine/headless/browser/cookie/cookie_test.go",
    "content": "package cookie\n\nimport (\n\t\"testing\"\n\n\t\"github.com/go-rod/rod/lib/proto\"\n)\n\nfunc TestShouldBlockRequest(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\turl             string\n\t\tresourceType    proto.NetworkResourceType\n\t\tinitiatorDomain string\n\t\twant            bool\n\t}{\n\t\t{\n\t\t\tname:            \"should block trustarc consent notice\",\n\t\t\turl:             \"https://consent.trustarc.com/notice?domain=hackerone.com\",\n\t\t\tresourceType:    proto.NetworkResourceTypeScript,\n\t\t\tinitiatorDomain: \"hackerone.com\",\n\t\t\twant:            true,\n\t\t},\n\t\t{\n\t\t\tname:            \"should not block trustarc for excluded domain\",\n\t\t\turl:             \"https://consent.trustarc.com/notice\",\n\t\t\tresourceType:    proto.NetworkResourceTypeScript,\n\t\t\tinitiatorDomain: \"forbes.com\",\n\t\t\twant:            false,\n\t\t},\n\t\t{\n\t\t\tname:            \"should not block non-matching URL\",\n\t\t\turl:             \"https://example.com/other-path\",\n\t\t\tresourceType:    proto.NetworkResourceTypeScript,\n\t\t\tinitiatorDomain: \"hackerone.com\",\n\t\t\twant:            false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ShouldBlockRequest(tt.url, tt.resourceType, tt.initiatorDomain)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"ShouldBlockRequest() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/browser/cookie/rules.json",
    "content": "[\n  {\n    \"id\": 1,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/iubenda_cs\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"radiomontecarlo.net\",\n        \"video.repubblica.it\",\n        \"video.lastampa.it\",\n        \"nablawave.com\",\n        \"buondi.it\",\n        \"tgcom24.mediaset.it\",\n        \"mediasetinfinity.mediaset.it\",\n        \"mediaset.it\",\n        \"telerama.fr\",\n        \"skuola.net\"\n      ]\n    }\n  },\n  {\n    \"id\": 2,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccm19_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"ccm19.de\"]\n    }\n  },\n  {\n    \"id\": 3,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"static.clickskeks.at\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 4,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/amp-user-notification-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 5,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sn.sanoma.fi/js/sccm\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 6,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/api/rest/settings/public?fields=endUserAgreement\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 7,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"congstar-media.de/fileadmin/cpolicy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 8,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"connect.danone.es\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 9,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/components/es6/PrivacyPolicy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 10,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"codice.shinystat.it/cgi-bin/getcod.cgi\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 11,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"whitepress.pl/common/pltk/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 12,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"js.hs-analytics.net/analytics\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 13,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jsdelivr.net/wp/wp-slimstat\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 14,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/dsvgo.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 15,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Flurrybox_EnhancedPrivacy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 16,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"static.axept.io/sdk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 17,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tarteaucitron.css\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 18,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"notice.sp-prod.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 19,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"htmedia.in/analytics-js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 20,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"campact.containers.piwik.pro\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 21,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"containers.piwik.pro\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 22,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wpcc.io/lib\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 23,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tag.goadopt.io\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 24,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/supi/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 25,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"omnigrafitalia.it/policy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 26,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/edgecastcdn.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 27,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/koriolis_gtm/theshield\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 28,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"fourmizz.fr/rgpd\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 29,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"hu-manity.co/hu-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 30,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ConsentManager,Sticky\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 31,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/rgpd/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 32,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/module-rgpd/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 33,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rcs_cpmt/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"video.corriere.it\", \"video.gazzetta.it\"]\n    }\n  },\n  {\n    \"id\": 34,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bloc/django/ckcsfrg\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 35,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ccm19.vucx.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 36,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/avia-bootstrap/js/klaro/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 37,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"exm-medien.de/cman\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 38,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"e2d-cms.de/cman\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 39,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/usercentrics-sdk/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 40,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cc.dalten.cz/ccJs\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 41,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccm19os/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 42,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"transcend.io/cm/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 43,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/avisopcdidomi\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 44,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccm19/public/app\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 45,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"webcache.datareporter.eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 46,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"webcache-eu.datareporter.eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 47,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.realpeoplemedia.co.uk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 48,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets-usercentrics/uc-version\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 49,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Sticky2,ConsentManager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 50,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cnilCookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 51,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"basketballbelieve.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 52,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"app.usercentrics.eu/browser-ui/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"kicker.de\", \"kicker.ch\"]\n    }\n  },\n  {\n    \"id\": 53,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cc.anytrack.de/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 54,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag/ctm/business-insurance/prod/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 55,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/GDPRPanelComponent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 56,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"aida.de/assets/coobann/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 57,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ModalEngage,ConsentManager,Sticky\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 58,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/coolkies-walkies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 59,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tagcommander/tc_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 60,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/media/plg_system_cookieconfirm\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 61,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie_meldung.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 62,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiewarning.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 63,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiewarning4.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 64,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"catalogocoop.it/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 65,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/mu-plugins/cookie_notifier\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 66,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"freegamehosting.eu/js/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 67,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jp2w.pl/a/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 68,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"https://cdn.serviceform.com/serviceform-tools/privacy/sf-privacy-partner.js?v=nethit\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"https://www.lumise.se/\"]\n    }\n  },\n  {\n    \"id\": 69,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"talktalk.co.uk\",\n        \"blackboard.com\",\n        \"kayak.pl\",\n        \"gamersgate.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 70,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/inc/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 71,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"metpartner.pl/cookie/info_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 72,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"catalogo.pusc.it/pusc_jquery.cookie.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 73,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie_law.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 74,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"lebendiges-aachen.de/includes/javascript/cookiechoices/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 75,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.reedbusiness.nl/script/cookiechecker/cookiejs.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 76,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/javascript/garante-privacy/js-cookie-master\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 77,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cookie-compliance\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 78,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.webgenerator.nl/_NoCDN/Javascript/CookieBar/cookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 79,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiesamtykke.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 80,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/mod_pescookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 81,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesdirective\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 82,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/webapps/bb-cookie-disclosure\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 83,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-use-policy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 84,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.orangegames.com/assets/js/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 85,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-policy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"newpharma.be\"]\n    }\n  },\n  {\n    \"id\": 86,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ico_cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 87,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/comun/cookie_comercial.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 88,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"hogarutil.com/js/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 89,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookielaw.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 90,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bootstrap-cookie-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 91,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieDisclaimer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 92,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/public/scripts/cookie/popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 93,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gartneriraadgivningen.dk/skinCss/website/js/cookie/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 94,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"blackboard.com\",\n        \"kayak.pl\",\n        \"portal.easygreenenergy.at\",\n        \"oekostrom.at\",\n        \"gin-rummy-online.com\",\n        \"gamersgate.com\",\n        \"spielemax.de\",\n        \"tonershop.at\",\n        \"plus-gp-joule.de\"\n      ]\n    }\n  },\n  {\n    \"id\": 95,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsentpopup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 96,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiePolicyEUPopin\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 97,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookielawscript.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 98,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiepolicy-client.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 99,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-disclaimer.lemm.de/cd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 100,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiebar-init.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 101,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie_consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"check24.de\"]\n    }\n  },\n  {\n    \"id\": 102,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiewarning2.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 103,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"phufart.pl/info_o_cookie_utf8.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 104,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieInfo.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 105,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"oleificiozucchi.it/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 106,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieAccept.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 107,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"videoland.com/external/cookiewall\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 108,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesDirective.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 109,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-law-plugin_en.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 110,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/libernazione-utils/js/libcookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 111,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"aegon.nl/data/cookiewall\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 112,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cultizm.com/templates/c2012light/javascript/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 113,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieprivacygenerator.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 114,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogcindario.com/cookie.php\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 115,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"promoracing.it/cookies/popup.cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 116,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"harazd.net/info_o_cookie_utf8.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 117,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie_acceptance_modal.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 118,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"eurail_responsive_law_cookie_banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 119,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dometic/dist/components/cookiescomponent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 120,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"basiszinssatz.info/cookiescript.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 121,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"leporc.com/sites/all/modules/custom/info_cookies/info_cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 122,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"static.origos.hu/s/js/custom/origo/accept-cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 123,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jquery-easy-eu-cookie-law.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 124,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"shop.szakalmetal.hu//js/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 125,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.nwmgroups.hu/s/js/custom/origo/accept-cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 126,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bootsticks.npage.de/assets/js/cookieconsent.latest.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 127,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.it/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 128,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.at/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 129,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.es/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 130,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.ee/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 131,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.pl/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 132,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.cz/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 133,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.dk/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 134,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.ie/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 135,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.fr/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 136,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.si/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 137,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.hu/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 138,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.sk/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 139,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.se/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 140,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.fi/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 141,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.lt/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 142,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.gr/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 143,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.ro/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 144,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.bg/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 145,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.be/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 146,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.hr/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 147,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.de/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 148,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.pt/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 149,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.nl/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 150,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.no/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 151,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.is/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 152,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.cl/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 153,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.lv/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 154,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.ch/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 155,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.ba/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 156,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.lk/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 157,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.ru/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 158,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.com/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 159,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.co.uk/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 160,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"blogspot.ca/js/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 161,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ext/zt_popupcookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 162,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"iti.si/deljene_datoteke/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 163,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/responsive-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 164,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"skm.warszawa.pl/js/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 165,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookie-info.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 166,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_law/plugin.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 167,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jslibrary/cookiewarning?\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 168,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cookie-notice\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"kapitalkontor.com\", \"barzahlen.de\"]\n    }\n  },\n  {\n    \"id\": 169,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"airnewzealand.co.nz/vbook/actions/cookieconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 170,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cg_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 171,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"grsuk.com/scripts/cookie-info.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 172,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-consent.es5.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 173,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookielaw_mip.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 174,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"informacja_cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 175,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiefix.dynamicline.hu/fixcookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 176,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 177,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-policy.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 178,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/eu-cookie-law-wp-cookie-law/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 179,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"eurocookie.galilcloud.wixapps.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 180,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-terms.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 181,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookieStatement.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 182,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wawona.hu/cookie-box/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 183,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/components/cookiescomponent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 184,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesinfo/cookieinfo.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 185,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent/cookie-consent.umd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 186,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebanner.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"huk24.de\",\n        \"huk.de\",\n        \"adtipp.de\",\n        \"nectar.com\",\n        \"neckermann.de\",\n        \"exali.de\",\n        \"raiplaysound.it\",\n        \"check24.de\"\n      ]\n    }\n  },\n  {\n    \"id\": 187,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/common/cpol/cookiepolicy.php\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 188,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-widget/latest/cookiewidget.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 189,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"static.talparadio.nl/js/cookiecheck.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 190,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bmedonline.es/js/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 191,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consentcookie.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 192,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie.consent.is\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 193,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-notification.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 194,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"filer_-_cookie_disclaimer_ny/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 195,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cpresources/cookieconsent/js/cookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 196,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"layer22.net/scripts/cookies-no-conflict-naxa\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 197,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiecuttr-eu-cookie-law-compliance/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 198,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jquery.eu-cookie-consent.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 199,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebar.jquery.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 200,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"trashmail.com/js/cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 201,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gratiz.nl/cookie-script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 202,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-service/js/client.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 203,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/euc_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 204,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/web/components/cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 205,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.cookiesdirective.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"servizionline.gruppoascopiave.it\"]\n    }\n  },\n  {\n    \"id\": 206,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-consent.azureedge.net/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 207,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jquery.cookie.policy.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 208,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/prettycookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 209,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/accept-ad-targeting/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 210,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"api.cookielaw.eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 211,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"api.cookielaw-script.it\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 212,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiealert.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"viessmann.com.pl\"]\n    }\n  },\n  {\n    \"id\": 213,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/external/ico.cookie.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 214,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/script/ico.cookie.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 215,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiewall.es5.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 216,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiecompliance.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 217,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/remote/v1/cookie-notification\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 218,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-warning.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 219,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookienotice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"betterworldbooks.com\"]\n    }\n  },\n  {\n    \"id\": 220,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"private.dmscookie.com/scripts/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 221,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tgory.sr.gov.pl/js/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 222,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eu-cookie-law.js?browserId\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 223,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.innershed.co.uk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 224,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/mu-plugins/cookie_notifier/cn.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 225,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebar_tls.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 226,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/smart-cookie-kit/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 227,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-notice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 228,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookiePolicy/resources/scripts/cookie.policy.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 229,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/cookieconsent/js/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 230,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jquery.cookiepol.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 231,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-bar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 232,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie/cc_min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 233,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookielaw/piskotkar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 234,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"myportalcms.com/template/js/mp_acookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 235,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieplugin/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 236,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-message.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 237,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 238,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"doitonlinemedia.nl/global-js/avg-cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"palazzogroep.nl\"]\n    }\n  },\n  {\n    \"id\": 239,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy.digimedia.com/check_cookie_country_code.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 240,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fortune_cookie_popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 241,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiecsnt2/js/cookie.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 242,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-note.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 243,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"policy.app.cookieinformation.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"kartor.eniro.se\",\n        \"kart.gulesider.no\",\n        \"kort.degulesider.dk\",\n        \"map.krak.dk\",\n        \"altibox.no\",\n        \"babysam.dk\",\n        \"elkjop.no\",\n        \"minaftale.dk\",\n        \"skousen.dk\",\n        \"skousen.no\",\n        \"whiteaway.com\",\n        \"whiteaway.no\",\n        \"whiteaway.se\",\n        \"dvdoo.dk\"\n      ]\n    }\n  },\n  {\n    \"id\": 244,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookienotice/Js/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 245,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/fortune-cookie-consent-policy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 246,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdl-cookiebar.tnt-digital.com/js/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 247,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/nl-cookie-law.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 248,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieaccept.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 249,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ercookiebar.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 250,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiewarn.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 251,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-hinweis/script-v2.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 252,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookieMessage.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 253,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets/js/sera/web/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 254,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiebanner.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 255,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/z7_cookiemanager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 256,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent/js/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"kawalekkodu.pl\"]\n    }\n  },\n  {\n    \"id\": 257,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieconsentnotice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 258,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/tnzcookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 259,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/gdpr-cookie-compliance\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 260,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/components/cookie-modal/cookie-modal.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 261,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-window.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 262,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiewall-inline-for-popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 263,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cc-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 264,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiePolicyV4.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 265,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/cookiechoices.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 266,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-widget/bootstrap.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 267,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.tipsy.cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 268,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie_choices.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 269,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/wp-cookie-allow/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"learndutch.org\"]\n    }\n  },\n  {\n    \"id\": 270,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cookiemedia/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 271,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Resources/Public/js/cookiebar.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 272,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plugins/system/cookiespolicynotificationbar\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 273,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieassistant.com/widget.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 274,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/helper-scripts/cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 275,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/msifestiwal/Scripts/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 276,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-overlay-pl.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 277,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.notify.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 278,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cpol/cookiepolicy.php\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 279,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-manager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 280,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"dtt.taleo.net/careersection/theme/381362/1547022019000/en/theme/js/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 281,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/cookies/cookiesLayer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 282,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookiePolicyGa/cookiepolicyga.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 283,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sdc/cookie_consent.html\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 284,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tmo_cookies/Resources/Public/Javascript/CookieSettings.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 285,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"deltaxmultimedia.com/cookielaw\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 286,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/web/components/cookieusage\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 287,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/eagerly-tools-cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 288,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/scripts/cookiebar\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 289,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/resources/CookieConsent/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 290,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"buro-3.nl/cookie/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 291,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ll_cookie_bar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 292,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"api.cookiemonster.is/embed\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 293,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/g361-cookies-consents\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 294,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Scripts/Cookies/cookieacceptance.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 295,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/extimages/scripts/ukcookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 296,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.aptelink.pl/nc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 297,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"eu_cookie_banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 298,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eu_cookie_compliance.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 299,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dc-cookie-privacy-settings.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 300,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesPolicy/static/lib.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 301,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiewarning-nosql.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 302,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.bpcookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 303,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/resources/js/cookie/cookie-bar\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 304,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieConsentDialog.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 305,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/bc-cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 306,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/idxcookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 307,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/javascript/cookieser.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 308,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesWidget/widget.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 309,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.cookiebot.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"storyhouseegmont.dk\",\n        \"storyhouseegmont.no\",\n        \"storyhouseegmont.se\",\n        \"egmont.com\",\n        \"stadtmobil.de\",\n        \"wwf.fi\",\n        \"login.nos.pt\",\n        \"asambeauty.com\",\n        \"cineplex.de\",\n        \"berlingske.dk\",\n        \"digimon.kochfilms.de\",\n        \"kino.dk\",\n        \"hoyavision.com\",\n        \"linak.es\",\n        \"linak.de\",\n        \"cajamar.es\",\n        \"digitaltrends.com\",\n        \"mein-grundeinkommen.de\",\n        \"werder.de\",\n        \"finanzmarktwelt.de\",\n        \"danbolig.dk\",\n        \"bt.dk\",\n        \"scubadiving.com\",\n        \"biomarkt.de\",\n        \"harzwasserwerke.de\",\n        \"stern.de\",\n        \"dasinvestment.com\",\n        \"derivate.bnpparibas.com\",\n        \"werkenbijlidl.nl\",\n        \"swspremberg.de\",\n        \"nngroup.com\",\n        \"bankia.es\",\n        \"bergbauernmilch.de\",\n        \"spiele-kostenlos-online.de\",\n        \"ekstrabladet.dk\",\n        \"epochtimes.de\"\n      ]\n    }\n  },\n  {\n    \"id\": 310,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccp-sites/components/structure/cookie-notification/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 311,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-info.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 312,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.algo.at\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 313,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiehub.net/c\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 314,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/widgets/eu-cookie-law/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 315,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"app.cookieyes.com/client_data\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 316,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/skinCss/website/js/cookie/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 317,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"freeprivacypolicy.com/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 318,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-module/dist\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 319,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieOptIn.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 320,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookie_banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 321,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery-cookies-alert.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 322,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookielaw.emea.fcagroup.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 323,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/tnk-cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 324,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiepopup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 325,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.cookiefirst.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"swietawdomu.pl\",\n        \"deutschesapothekenportal.de\"\n      ]\n    }\n  },\n  {\n    \"id\": 326,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-policy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 327,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/liquorice-cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 328,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-switch.viminds.de/api\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 329,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"skyscnr.com/sttc/oc-registry/components/cookie-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 330,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/cookiesplus/views/js/cookiesplus.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 331,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mws-cookie-solution.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 332,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ajax/libs/cookieconsent2\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"membersuite.com\",\n        \"la-becanerie.com\",\n        \"rijbewijskeuringennederland.nl\",\n        \"download.pixelexperience.org\",\n        \"ceramtec-group.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 333,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"talktalk.co.uk\",\n        \"codexis.cz\",\n        \"lacoste.com\",\n        \"huuray.se\",\n        \"indiearenabooth.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 334,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookienotice-bootstrap.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 335,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cookieEuGH\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 336,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 337,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/cookie-notice\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 338,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiechoices.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 339,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 340,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieconsent.popupsmart.com/src/js/popper.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 341,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"flixbus.com/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 342,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/jscookieconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 343,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.cookie-policy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 344,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-alert.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 345,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"up-cookiemon.wavecdn.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 346,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieBanner.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"adtipp.de\",\n        \"nectar.com\",\n        \"neckermann.de\",\n        \"exali.de\",\n        \"raiplaysound.it\",\n        \"check24.de\"\n      ]\n    }\n  },\n  {\n    \"id\": 347,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/politica-cookies/leyCookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 348,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/trwcookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 349,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dc_components/site/cookie-control/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 350,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-manager.de/cookie-manager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 351,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pxpcookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 352,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie/uccb-main\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 353,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_consent_min_js.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 354,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/onlinemanufaktur-cookie-notice/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 355,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mq_cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 356,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/static/cookies/cookie-layer-wc-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 357,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-bar/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"vectra.pl\", \"urbanista.de\"]\n    }\n  },\n  {\n    \"id\": 358,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebar/scripts.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 359,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fileadmin/cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 360,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieopt-min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 361,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/micado.web.dsgvo.cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 362,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wip.pl/js/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 363,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/docroot/js/app/modules/cookies-selection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 364,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/cookieman/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 365,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiecode.dist.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 366,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_agreement_dialogue.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 367,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"googleapis.com/55_cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 368,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/nsd-cookie-banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 369,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/responsive-eu-cookie-notice\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 370,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/master-popups-cookieplus\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 371,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-banner-one.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 372,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiemanagement.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 373,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/mod_eu_cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 374,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.cookiefy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 375,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"websitepolicies.com/lib/cookieconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 376,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/aip_cookie_law\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 377,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie.gg/c\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 378,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plg_system_vpcookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 379,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/p8-cookie-bar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 380,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-banner/cb.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 381,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/echonetcookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 382,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-policy-en.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 383,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bb_cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 384,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/?tx_z7cookiemanager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 385,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/kmacookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 386,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/_hw_cookie_dialog.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 387,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-settings-ui/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 388,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookietrust.eu/script\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 389,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bs-cookie.staging.springbok.agency\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 390,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-banner.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"nice.org.uk\"]\n    }\n  },\n  {\n    \"id\": 391,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/WebUI/Cookies/allowcookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 392,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/SharedComponents/bundle-scripts/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 393,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent.klaro.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 394,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookies_bar\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 395,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modal/cookie-wall/?modal_view=true\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 396,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/better_cookie_consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 397,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/jquery-eu-cookie-law-popup\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 398,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"flipdish-cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 399,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookie-law-ansi\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 400,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieconsent.syreta.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 401,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/deferred/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 402,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/rodo-cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 403,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/hw-cookie-dialog.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 404,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/accept-cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 405,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_warning.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 406,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plugins/cookies/tinybox.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 407,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/web/components/cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 408,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-bar.salessquad.co.uk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 409,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieMunchr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 410,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"nicmanager.com/static/cookie_guideline\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 411,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"traderfox.de/lib/tfcookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 412,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/amadeus-plugin-cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 413,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rh-cookieconsent-microsites.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 414,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/components/content/cookie-overlay/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 415,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiemon.atcom.gr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 416,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"nbcsports.com/cookie-ack.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 417,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/gdpr/gdpr-cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 418,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.ihavecookies.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"wisla-plock.pl\"]\n    }\n  },\n  {\n    \"id\": 419,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/b2b-market/src/addons/cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 420,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/vue/components/cookie-monster\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 421,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/do-you-want-cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 422,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bascom.nl/cookies/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 423,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fluvius-eu-cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 424,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/lyxor-cookies-disclaimer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 425,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/grt-cookie-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 426,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bundles/cookiebar\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 427,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/avia-snippet-cookieconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 428,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/user-cookie-banner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 429,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/we_cookie_consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 430,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/vp-cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 431,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/system/modules/cookiecontrol/assets/js/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 432,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieConsentNotif.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 433,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/ne_cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 434,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-bar.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 435,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ilovecookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 436,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/luxcookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 437,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiehint/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 438,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieOptIn.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 439,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sg_cookie_optin/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 440,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebox.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"footroll.pl\"]\n    }\n  },\n  {\n    \"id\": 441,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.privacy.protection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 442,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/new-cookie-policy.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 443,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiehint.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 444,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/public-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 445,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mod_jt_cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 446,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/dist/cookieAcceptance\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 447,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebanner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 448,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/acc.cookienotification.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 449,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiepolicy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 450,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie_api/cccframe\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 451,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookienote-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 452,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/metro?sc_device=webcomponent&components=cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 453,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_msg.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 454,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_info.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 455,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"js.cookietagmanager.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 456,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets/mnd-cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 457,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js_cookies_legal.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 458,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ys_cookie_consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 459,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-v2/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 460,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/beautiful-and-responsive-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 461,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie/banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 462,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/widget-module-cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 463,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/lightweight-cookie-notice/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 464,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ocean-cookie-notice/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 465,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jst_eu_cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 466,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"procookie.by.nf\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 467,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 468,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jquery-eu-cookie-law-popup\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 469,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cp_cookieconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 470,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.ae-webdesign.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 471,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ws5_eucookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 472,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieConsent/cookieModal.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 473,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/consent-cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 474,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.fo/qookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 475,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/hw-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 476,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"labs.strava.com\", \"crossfit.com\"]\n    }\n  },\n  {\n    \"id\": 477,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/w4.cookiebar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 478,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cookielay/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 479,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"plugin-cookie-consent/build\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 480,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie/onetrust/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 481,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-settings-modal/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 482,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/components/consent-cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 483,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-service.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 484,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent.bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 485,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/lgpd-cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 486,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cookies-and-content-security-policy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 487,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiebox_uc_build.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 488,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gen/cookie-notification\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 489,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesjsr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"lcp.fr\"]\n    }\n  },\n  {\n    \"id\": 490,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/eu-cookies-bar/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 491,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieOverlay-view.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 492,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/Widgets/cookieWidget\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"losteria.de\"]\n    }\n  },\n  {\n    \"id\": 493,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/minified/cookiecontrol.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 494,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/ab1d_cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 495,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/module/kabimba_cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 496,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/woody-addon-cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 497,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/__enzuzo-cookiebar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 498,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_policy/ack.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 499,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"northernpowergrid.com\"]\n    }\n  },\n  {\n    \"id\": 500,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/zedwcookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 501,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiedisclosure/core.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 502,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie.thynk.media/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 503,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/usercentrics/cookiebox\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 504,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-tool/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 505,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-notification.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 506,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"onetrust.com/cookieconsentpub\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"fujifilm-x.com\"]\n    }\n  },\n  {\n    \"id\": 507,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/dp_cookieconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 508,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 509,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/om_cookie_main.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 510,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.rgpd-cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 511,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bandeau_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 512,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dist/js/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 513,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"sonnenverlauf.de\",\n        \"mondverlauf.de\",\n        \"leanlibrary.com\",\n        \"retrogames.cc\",\n        \"exagon.de\",\n        \"sozialversicherung-kompetent.de\",\n        \"varcevanje-energije.si\",\n        \"khl.ru\",\n        \"bauen-und-heimwerken.de\",\n        \"carport-diagnose.de\",\n        \"tricount.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 514,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieConsentApp.js.gz\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 515,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fileadmin/templates/cookie/fc_thin.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 516,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.cookiecode.nl\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 517,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/nd-cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 518,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent-all.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 519,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieBanner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 520,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-wall.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 521,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.giant.cz/assets/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 522,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.praguebest.cz/dist\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 523,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-prompt/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"gestiontv.vodafone.es\"]\n    }\n  },\n  {\n    \"id\": 524,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bootstrap-cookie-consent-settings.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 525,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rails_cookie_consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 526,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rgpd/js/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 527,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieBannerLoader.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 528,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieform.pl/assets/js/plugin\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 529,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets/components/cookiemanager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 530,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ewcookienoteext.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 531,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/clientlibs/components/content/cookie/cookielayer\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 532,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rm-cookieconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 533,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-banner-vue\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 534,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiecdn.com/cwc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 535,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/om-gdpr-cookie-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 536,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn-cookieyes.com/client_data\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 537,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"digitalsternemarketing.de/dscookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 538,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modal-acceptare-cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 539,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-bar/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 540,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookieconsent.blob.core.windows.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 541,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/all-cookieconsent-js.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 542,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-browser-alert-ui.fragment.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 543,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tcf/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 544,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bsgdprcookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 545,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \".cookiehub.eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 546,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"traveljuice-engines.prod.traveljuice.fr/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 547,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eu_cookie_compliance_override.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 548,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/smart-eu-cookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 549,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 550,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Pictorium/scripts/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 551,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jsLib/gdpr/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 552,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieBar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 553,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cc.controlcookies.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 554,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieNotice.build.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 555,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ux.ui-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 556,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesPopUp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 557,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/in2cookiemodal\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 558,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-banner/cookie-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 559,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/polityka-prywatnosci/js/cookie-overlay\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 560,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-portlet/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 561,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/simple-gdpr-cookie-compliance/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 562,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-consents.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"posta.sk\"]\n    }\n  },\n  {\n    \"id\": 563,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"theme-cookie/app/cookie.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 564,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"plugins/ShprCookieFavour/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 565,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.ptj.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 566,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieModal/clientlib\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 567,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-banner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 568,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dxcookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 569,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/real-cookie-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 570,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sohoshopcookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 571,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"am-static.com/cookie-banner/sdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 572,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/marked-cookie-consent-web/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 573,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.cookielaw.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"cnn.com\"]\n    }\n  },\n  {\n    \"id\": 574,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/borlabs-cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 575,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/gdpr-cookie-compliance/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 576,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/etagen_cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 577,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_overlay.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 578,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plugins/cookies-and-content-security-policy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 579,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieBar.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 580,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiedialog/cookieutility.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 581,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/creare-eu-cookie-law-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 582,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jsdelivr.net/npm/@finsweet/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 583,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieseubox.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 584,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie/cc_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 585,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieConsent.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"fransdewitte.nl\"]\n    }\n  },\n  {\n    \"id\": 586,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/category-assets/experiences/recurring/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 587,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/storage/ui/cookies-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 588,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiedisturber/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 589,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/lightweight-cookie-notice\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 590,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/dm_cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 591,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-dialogue.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 592,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dist/scripts/cookie-control\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 593,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"am-static.com/cookie-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 594,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/marked-cookie-consent-web/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 595,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"app.falconcookie.de/storage\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 596,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/brd_cookies_consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 597,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/hicookielaw/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 598,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/kbv-cookieconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 599,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-mycity.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 600,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/components/src/cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 601,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/webtoffee-gdpr-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"altheaprovence.com\"]\n    }\n  },\n  {\n    \"id\": 602,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.neuca24.pl/nc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 603,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies/manage-cookies-runtime\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 604,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/ppw_cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 605,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-widget/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 606,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/pages/js/cookie-dialog.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 607,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3temp/assets/compressed/cookieman\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 608,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiethough.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 609,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"beyond-cookiebanner.de/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 610,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets/js/cookie_consent_manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 611,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/viva-cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 612,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieMessage.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 613,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/michnhokn/cookie-banner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 614,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ddjnocookie.com/prod_public\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 615,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/cookie_policy/frontend/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 616,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 617,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpsystem.eu/pl/cookiesprivacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 618,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fcc-cookie-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 619,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-cookie-consent.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 620,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieConsentScript\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 621,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookienoticepro.script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 622,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dsgvo/js/cookiemonster\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 623,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_consent_js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 624,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent-request.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 625,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wko.at/gcm/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 626,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wko.at/css-js/scripts/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 627,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/frontend-core/js/cookieBox.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 628,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiemanager.dk/js/cm.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 629,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mod_pixim_cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 630,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tmgonlinemedia.nl/consent/script/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 631,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy.ariadneathome.nl/script/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 632,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy.vtwonen.nl/script/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 633,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wpjslib-chunk-consent-form.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 634,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/consentbar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 635,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdprpro/views/js/gdpr-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 636,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/consent-banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 637,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"s.clickiocdn.com/t/consent_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 638,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jppol-consent/js/bootstrap.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 639,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/opbox-rodo-consent-modal/index\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 640,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"optanon.blob.core.windows.net/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 641,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"libertatea.ro/consent/config.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 642,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent-notice.magix.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 643,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/taunton-user-consent-eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 644,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consensu.org/t/consent_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"softzone.es\"]\n    }\n  },\n  {\n    \"id\": 645,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consentmanager.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"sourceforge.net\",\n        \"webfail.com\",\n        \"sudoku-aktuell.de\"\n      ]\n    }\n  },\n  {\n    \"id\": 646,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consently.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 647,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"s.adroll.com/j/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 648,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-panel-vue.chunk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 649,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.theneweuropean.co.uk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 650,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy-consent-banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 651,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"thezen.garden/projects/zenconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 652,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"smartconsent.ro/js/smart-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 653,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/webflow-consent-manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 654,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"app.consentassist.com/widget.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 655,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"s.luxupcdnc.com/t/consent_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 656,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.appconsent.io\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"lefigaro.fr\"]\n    }\n  },\n  {\n    \"id\": 657,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.23g.io\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 658,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"browser-consent-front.coco.s-cloud.fi\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 659,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets/consent-manager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 660,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.weersvoorspelling.nl/v1\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 661,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.webmasterplan.com/v2\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 662,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/adsconsent.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 663,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.truste.com/get?name=notice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 664,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.trustarc.com/notice\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"tripit.com\",\n        \"concursolutions.com\",\n        \"fortune.com\",\n        \"formula1.com\",\n        \"forbes.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 665,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent-manager.metomic.io\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 666,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.uniconsent.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"photopea.com\"]\n    }\n  },\n  {\n    \"id\": 667,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-banner-bootstrap\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 668,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tmgrup.com.tr/tmd-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 669,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/consent-modal.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 670,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/api/snippets/js/consent-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 671,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sixfifty.com/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 672,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 673,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"choices.consentframework.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 674,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cache.consentframework.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 675,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consentserve.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 676,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"analytics-consent-manager.azureedge.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 677,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mgm_consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 678,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"hoffmann-group.com/common/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 679,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dv_t3_consent_management/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 680,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gsso_consent_manager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 681,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"avandor.com/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 682,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/data_consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 683,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"smart.idmnet.pl/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 684,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"kalevakonserni.fi/consent/gravito\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 685,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bundle-consent-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 686,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modal-consent-component.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 687,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-management/_include/init_consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 688,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"pitcom-webanalyse.de/dsgvo/consent-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 689,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wrd-aws.com/consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 690,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/ag-consentmanager-no-bootstrap/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 691,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/static/consent-dialog.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 692,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/opbox-gdpr-consents/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 693,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/consent-manager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 694,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ampproject.org/v0/amp-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"smartdroid.de\", \"lelum.pl\"]\n    }\n  },\n  {\n    \"id\": 695,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.trustarc.com/v2/notice\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 696,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/resources/onetrust/js/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 697,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-manager/lazy-consent-manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 698,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/addons/consent_manager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 699,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/npm/ez-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 700,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/piwik-consent-banner-script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 701,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fileadmin/template/js/cconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 702,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/kconsent/kconsent29.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 703,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-layer/js/consent-layer-loader.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 704,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tm-gdpr-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 705,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"snigelweb.com/adconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 706,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sncmp.com/adconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 707,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consentmanager.net/delivery/js/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"winfuture.de\", \"infranken.de\"]\n    }\n  },\n  {\n    \"id\": 708,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent-manager.confirmic.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 709,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consentbanner.de/public/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 710,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"classistatic.de/consent-statics\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 711,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consentcheck.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 712,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-consent-management-platform/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 713,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/drdsgvo-consent-script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 714,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-consent.bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 715,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent/civic-bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 716,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"totalweb.gr/gdpr/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 717,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/epaas.consentdrawer.bundle\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 718,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consentbanner-fragment/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 719,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"altravia.com/connector/consent_mode.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 720,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consentmanager.net/delivery/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"winfuture.de\", \"infranken.de\"]\n    }\n  },\n  {\n    \"id\": 721,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/taunton-user-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 722,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fileadmin/templates/js/cconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 723,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/okf-euconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 724,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.daa.net/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 725,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-consent-banner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 726,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-management-app/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 727,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.hr/delivery/js/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 728,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bundles/consent-init?v=\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 729,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/acc.consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 730,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.scm-verlagsgruppe.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 731,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/consent-magic-pro\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 732,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"7gra.us/consentbar\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 733,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dgp-cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 734,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets/as24-cmp/consent-banner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 735,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/log-user-consents/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 736,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent-bist.de/public/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 737,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.digiapi.com/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 738,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"idealo.com/storage/cmp/consent-management.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 739,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.prointernet.com/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 740,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/coockieconsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 741,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consentmanager.net/delivery\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"winfuture.de\",\n        \"echo-online.de\",\n        \"stern.de\",\n        \"spar.hu\",\n        \"spar.hr\",\n        \"spar.at\",\n        \"spar.si\",\n        \"interspar.at\"\n      ]\n    }\n  },\n  {\n    \"id\": 742,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.comply-app.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 743,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"kesko.fi/kconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 744,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"unpkg.com/@segment/consent-manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 745,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wko.at/static/ct/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 746,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consenttool.haendlerbund.de/app.js?apiKey\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 747,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.extrazimut.net/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 748,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cs_consent_modal.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 749,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/gdpr_footer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 750,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/gdpr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 751,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/comun/avisopcgdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 752,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 753,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpr.internetbrands.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 754,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \".be/api/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 755,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \".nl/api/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 756,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"-gdpr-min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 757,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-transparency-apnxs/latest/gdpr-bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 758,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sitemaps.services.zam.com/gdpr_optout.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 759,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ibeugdpr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 760,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consensu.org/gdpr/cmp/gdpr-cmp-ui.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 761,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"s3.amazonaws.com/sitemaps.services.zam.com/gdpr_standalone.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 762,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sizzlegdpr.snippet.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 763,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/gdpr/messaging.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 764,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mdp.javascript.gdpr.min\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 765,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/emg-framework/public/js/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 766,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/emg-framework/assets/js/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 767,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/assets/js/gdpr.js?ver\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 768,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/injectable.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 769,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ishop-plugins/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 770,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"secure-cdn.mplxtms.com/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 771,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 772,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/am2-gdpr-public.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 773,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdprscript.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 774,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plugins/surbma-gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 775,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-banner.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 776,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"libraries.wmgartistservices.com/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 777,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdprmain/prod/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 778,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.thisisdone.com/gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 779,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpr-banner.awsmpsa.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 780,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"securebrainpull.com/gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 781,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpr500.com/widget/pandawidget/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 782,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bundle-gdpr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 783,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/CookieConsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"kayak.pl\"]\n    }\n  },\n  {\n    \"id\": 784,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/2018-gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 785,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-cmp-ui.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 786,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/gdpr.min.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 787,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plugins/gdpr/gdpr_ncoi.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 788,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/static/new/js/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 789,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/libs/gdpr/cmp/cmp.bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 790,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pgdpr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 791,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/policies/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 792,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/spd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 793,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"prizelogic.com/gdpr/third-party-optin.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 794,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 795,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wordpress-ptchrgdprplugin/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 796,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpr-wrapper.privacymanager.io\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"kinuskikissa.fi\",\n        \"geenstijl.nl\",\n        \"dailybuzz.nl\",\n        \"rtvnoord.nl\",\n        \"omroepbrabant.nl\",\n        \"futurezone.at\",\n        \"profil.at\",\n        \"kurier.at\",\n        \"weeronline.nl\",\n        \"lablue.de\",\n        \"vesti.bg\"\n      ]\n    }\n  },\n  {\n    \"id\": 797,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"evidon.com/pub/gdprnotice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 798,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/ct-ultimate-gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 799,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpr.mandarin-medien.de/manager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 800,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpr-tcfv2.sp-prod.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"computerbild.de\",\n        \"programme-tv.net\",\n        \"bold.dk\",\n        \"welt.de\",\n        \"sport1.de\",\n        \"ostsee-zeitung.de\",\n        \"sky.com\",\n        \"lvz.de\",\n        \"mein-schoener-garten.de\",\n        \"autobild.de\",\n        \"bike-bild.de\",\n        \"bz-berlin.de\",\n        \"travelbook.de\",\n        \"si.com\",\n        \"capital.fr\",\n        \"t3n.de\",\n        \"is.fi\"\n      ]\n    }\n  },\n  {\n    \"id\": 801,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"yastatic.net/s3/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 802,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/zgdpr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 803,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/pp_agreement.pc.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 804,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/cmp/cmpBundle.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 805,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/webmd.gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 806,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/motus-gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 807,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plugins/gdprprivacysetup/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 808,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc-gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 809,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"metronom.com/library/scripts/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 810,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/lin_gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 811,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tronic-gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 812,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-banner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 813,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/typo3conf/ext/dbb_gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 814,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-appliance.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 815,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"pdcc.gdpr.es\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 816,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-redirect.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 817,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/shared/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 818,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sdk-gdpr.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 819,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/gdpr-component\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 820,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdprDialog.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 821,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bluelabs-gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 822,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdprScriptComponent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 823,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"mediavine.com/tags/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 824,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"pubnation.com/tags/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 825,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ecos.am/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 826,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modal-gdpr.umd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 827,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr_notice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 828,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/lexon-gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 829,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/popin-gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 830,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modules/gdprpro/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 831,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gdpr-api.sharethis.com/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 832,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/media/plg_system_eprivacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"reshade.me\"]\n    }\n  },\n  {\n    \"id\": 833,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"recepttar.hu/js/privacy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 834,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.dmgmediaprivacy.co.uk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"metro.co.uk\", \"gbnews.uk\"]\n    }\n  },\n  {\n    \"id\": 835,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jssdk.privacy.pre.schibsted.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 836,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"secureprivacy.ai/secureprivacy-plugin/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 837,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/vendor/weka/privacykit/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 838,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy.clym.io/js/clym-widget.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 839,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/privacypolicy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 840,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bosch-privacy-settings-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 841,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/general/avada-privacy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 842,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"public.message-business.com/Javascript/mb.privacyManager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 843,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.reeltime.no/pm_assets/privacy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 844,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ftcguardian.com/privacy-update\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 845,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacypolicy.trgr.be/widget\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 846,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacyopt.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 847,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacypolicy/styles/all/template/remove_url.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 848,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"psi.schibsted.com/api/v2/privacy/notification\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 849,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/privacy-policy-info/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 850,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/e-privacy.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 851,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacyConsentBar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 852,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tagcommander.com/privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"labanquepostale.fr\"]\n    }\n  },\n  {\n    \"id\": 853,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"isgprivacy.cbsi.com/dist/optanon\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"cbsnews.com\",\n        \"startrek.com\",\n        \"insideedition.com\",\n        \"cbslocal.com\",\n        \"etonline.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 854,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/contao-privacy-center.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 855,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-policy.u-lab.nl\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 856,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"trustcommander.net/privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"tf1info.fr\", \"tf1.fr\"]\n    }\n  },\n  {\n    \"id\": 857,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"app.eprivacy-keeper.eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 858,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/en-privacy-notification/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 859,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy.claytonhomes.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 860,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"shopify.com/shopifycloud/privacy-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 861,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/enua-privacy-policy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 862,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"demotywatory.pl/res/js/privacy_policy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 863,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eprivacy/js/eprivacy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 864,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sf-tagomo-privacy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 865,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/s4s-privacy-module/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 866,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/shopsshort/privacy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 867,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"heg-cp.com/upm/privacy-manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 868,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tag=ui/privacy/CookiesConsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 869,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dock-privacy-settings.esm.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 870,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy.wum.rocks/public/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 871,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tagcommander/privacy_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 872,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/serviceform-tools/privacy/sf-privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 873,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy-dialog-tracking.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 874,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy/providers/CookiesDataProvider\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 875,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/contao-privacy-center\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 876,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/myagileprivacy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 877,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.lemonde.fr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 878,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.quantcast.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 879,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.nextday.media/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"vi.nl\", \"omroepwest.nl\"]\n    }\n  },\n  {\n    \"id\": 880,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.dreamlab.pl\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 881,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/media/cmp/int_cmp_banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 882,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consensu.org/delivery/cmp.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 883,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sipaof.mgr.consensu.org/sipacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 884,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/sourcepoint/sp-msg.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 885,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.cdntrf.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 886,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.mediavine.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 887,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmpCookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 888,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/library/cmp/cmp.bundle-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 889,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp-loader.choice.faktor.io\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 890,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/diyscmp.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 891,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bilsyndication.com/js/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 892,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bilsyndication.com/plugins/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 893,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.osano.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 894,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"vlitag.com/plugins/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 895,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.md-nx.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 896,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.opencmp.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"foraum.de\", \"idowa.de\"]\n    }\n  },\n  {\n    \"id\": 897,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.faktor.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"omroepwest.nl\", \"consent.talpanetwork.com\"]\n    }\n  },\n  {\n    \"id\": 898,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gravito.net/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 899,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdncmp.richaudience.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 900,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"heymatic.com/assets/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 901,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/js/vendors~cmpUi\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 902,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ustatik.com/public/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 903,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"mrf.io/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 904,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.cls.pm\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 905,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gravito.net/lightcmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 906,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"onetag-sys.com/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 907,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/truendo_cmp.pid.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 908,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.optad360.io\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 909,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"snigelweb.com/sncmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 910,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"seznam.cz/js/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 911,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pubtech-cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 912,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.pafo.fairbung.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 913,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sibbo.net/v2/sibbo-cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 914,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"24net.cz/resources/js/cmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 915,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/quorn-cmp.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 916,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmps.o2.cz/delivery\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 917,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tcf-cmp.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 918,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"fastcmp.com/fast-cmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 919,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"apps.ludostation.com/cmp/v2/cmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 920,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"mrdev-cmp/assets/js/script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 921,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.setupcmp.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 922,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.meteored.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 923,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"veodys.fr/api/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 924,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sibbo-cmp-core.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"atresplayer.com\"]\n    }\n  },\n  {\n    \"id\": 925,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gravito.net/alehdetcmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 926,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"fastcmp.com/fast-cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 927,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sibbo-cmp-loader.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 928,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.springernature.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 929,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"determinator.service-cmp.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 930,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"seznam.cz/js/cmp2/scmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 931,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"seznam.cz/js/cmp2/scmp-external.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 932,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gofundme.com/js/3.0/visitorCookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 933,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookiesDirective\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 934,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"CookieAccept/affirmation.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 935,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ariba.com/assets/scripts/classes/Ariba.Compliance.CookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 936,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"aldermore.co.uk/Scripts/Logic/CookieDisclaimer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 937,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tweeboo.com/r/js/CookieDirective\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 938,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jqueryCookieGuard\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 939,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jquery.smartCookie.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 940,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc-bar/cCookiesH.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 941,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieDirective.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 942,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/setPrivacyCookie.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 943,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/clientlib-webpack-publish/js/CookiesApp-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 944,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/CookieManager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 945,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bundle_CookieLegalNotice.prod.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 946,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/acceptCookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 947,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/i_CookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 948,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Amasty_GdprCookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"planet-cards.co.uk\",\n        \"sunkost.no\",\n        \"wondrium.com\",\n        \"nova-motors.de\",\n        \"moleonline.com\",\n        \"durstexpress.de\",\n        \"littlelunch.com\",\n        \"twinpack.nl\",\n        \"eckeroline.fi\",\n        \"eilles.de\",\n        \"xt500parts.com\",\n        \"cupper-teas.de\"\n      ]\n    }\n  },\n  {\n    \"id\": 949,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ts/components/CookieConsent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 950,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/SDG_CookieLayer.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 951,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/slr_js/allowCookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 952,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/javascript/component-CookieConsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 953,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/whCookieManager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 954,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Cookie-GetCookieModal\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 955,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/1990KB-CookieConsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 956,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Magento_Cookie/js/notices\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 957,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/iwCookieBanner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 958,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"blackboard.com\",\n        \"kayak.pl\",\n        \"gamersgate.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 959,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/CookieConsentNew\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 960,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieManagerUi.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 961,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/widgets/global/vendors~LegalCookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 962,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/getCookieConsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 963,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/kssCookieManager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 964,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ModalCookiesPrivacy.php\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 965,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookiesManager/CookiesManager.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 966,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/components/CookieManager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 967,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/SiteElements/Scripts/CookieBanner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 968,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/plugins/CookiePop/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 969,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/IntegerNet_CookieConsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 970,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/new_Cookiebanner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 971,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/feoCookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 972,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Plumrocket_CookieConsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"8020.net\"]\n    }\n  },\n  {\n    \"id\": 973,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/static/CookieManager/js/app\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 974,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/optInCookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 975,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ja/controlCookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 976,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utils/CookiePrompter\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 977,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Wamoco_CookieConsentUi/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 978,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/standaloneModalCookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 979,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Detco_CookieBanner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 980,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/vfConsentCookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 981,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Cookie-Interfrog/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 982,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"quantcast.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"sourceforge.net\",\n        \"tumblr.com\",\n        \"wpolityce.pl\",\n        \"player.livespotting.com\",\n        \"indy100.com\",\n        \"vi.nl\",\n        \"independent.co.uk\",\n        \"express.co.uk\",\n        \"joe.ie\",\n        \"joe.co.uk\",\n        \"standard.co.uk\",\n        \"avsforum.com\",\n        \"pcgamer.com\",\n        \"nfl.com\",\n        \"filmvandaag.nl\",\n        \"gamesradar.com\",\n        \"iol.pt\"\n      ]\n    }\n  },\n  {\n    \"id\": 983,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"gemius.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 984,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sddan.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 985,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"digitrust.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 986,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sharethis.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 987,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"webads.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 988,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"etarget.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 989,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"optad360.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"infogliwice.pl\"]\n    }\n  },\n  {\n    \"id\": 990,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"shinystat.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 991,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"dan.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 992,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ogury.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 993,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"inforpl.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 994,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"sibboventures.mgr.consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 995,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consensu.infor.pl\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 996,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ciasteczkowapolityka.pl/getscript\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 997,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bsl.nl/extern/smbv-incl/script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 998,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ssl.synovite-scripts.com/ut\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 999,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"zoneadsl.com/clientscript/cnil.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1000,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"angrybirdsmovie.net/site/scripts/nettracking4.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1001,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bobulous.org.uk/javascript-head-sitewide.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1002,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wroclaw.pl/portal/themes/js/script-rodo.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1003,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/scripts/pookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1004,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"metasrc.com/assets/javascripts/compliance.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1005,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"geo.fr/assets/scripts/sourcepoint.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1006,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/rodo/rodo_script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1007,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.praivacy.eu/scripts\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1008,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"script.getcomplied.com/scripts/complyWidget/assets/getCompliedListWidget.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1009,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Gpdr/assets/ccc-script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1010,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"kookiecheck.cz/static/script\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1011,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"legalblink.it/api/scripts/lb_cs.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1012,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dgsvo/script.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1013,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/cc/js/cc.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1014,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"common.i12.de/cms/file/plugin/dp/dp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1015,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/shapepress-dsgvo/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1016,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/iticonseil-rgpd/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1017,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/pixelmate/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1018,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/pixelmate-opt-in/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1019,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"static.trbo.com/plugin\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1020,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/fb-pixel-dsgvo/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1021,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"id-ward.com/static/idw_plugin\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1022,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ishop-plugins/ishop-cp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1023,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/rrze-legal\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1024,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/rrze-legal\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1025,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wp-content/plugins/jjarolim-tracking/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1026,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/acton/bn/tracker\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1027,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"etracker.de/optin_overlay.php\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1028,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/libraries/google/do-not-track.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1029,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tracking-permission-dialog.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1030,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"origin-images.wikia.com/fandom-ae-assets/tracking-opt-in\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1031,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tracking-opt-in.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1032,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/contaotrackingmanager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1033,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/peg_utils/tracking/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1034,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/kixsimpletrack/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1035,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rodo.js?pp_pr=\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1036,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/templates/rodo/rodo.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1037,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/rodo/rodo.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1038,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/rodo.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1039,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"rodo.agora.pl/agreement/check\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1040,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/min-js?f=js/rodo.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1041,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rodo_rmf\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1042,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/popup/rodo.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1043,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rodo/rodo.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1044,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rodo-agreement-popup.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1045,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"www.capitol.fr/streaming/cnil/cnil.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1046,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"prismamediadigital.com/cnil.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1047,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"newsletters.ftv-preprod.fr/cnil/js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1048,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Dock/DockContent/Cards/GDPRCard/index.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1049,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"nanoGDPR.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1050,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/GDPR/GDPR.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"3ds.com\"]\n    }\n  },\n  {\n    \"id\": 1051,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/GDPRPanelComponent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1052,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tdn.r42tag.com/lib/ut\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1053,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"c.betrad.com/pub/third.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1054,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"fundingchoices.google.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1055,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"fundingchoicesmessages.google.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\n        \"rtl.hr\",\n        \"fernsehserien.de\",\n        \"bbc.com\",\n        \"iol.pt\",\n        \"stooq.pl\",\n        \"stooq.com\"\n      ]\n    }\n  },\n  {\n    \"id\": 1056,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"spiffymachine.com/v2/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1057,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.userdatatrust.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1058,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.pubguru.com/pg.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1059,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"qualtrics.com/WRSiteInterceptEngine\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"logmeininc.com\"]\n    }\n  },\n  {\n    \"id\": 1060,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"a.svtrd.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"ns.nl\"]\n    }\n  },\n  {\n    \"id\": 1061,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"foolcdn.com/mms/resources/js/international-visitor-notice-js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1062,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"heraeus.com/media/system_files/special_applications/heraues_datapolicy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1063,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"libraries.wmgartistservices.com/pplightbox\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1064,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"assets.ubembed.com/universal\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1065,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"pi.pardot.com/analytics\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1066,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"api.useinsider.com/ins.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1067,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"a.optmnstr.com/app/js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1068,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"borderfree.com/v1/dist/cbt.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1069,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.justuno.com/mwgt\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1070,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"caringzinc.com/v2\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1071,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"dam.bbcchannels.com/m/2fmpg/js/outside-iframe.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1072,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"dam.bbcchannels.com/m/2fmph/js/outside-iframe.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1073,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.truendo.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1074,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"intelligentscissors.com/v2\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1075,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.adnuntius.com/adn.dmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1076,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cct-pubweb.com/ccpa\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1077,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"widgets.legalmonster.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1078,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"nexus.ensighten.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1079,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"einfachonline.com/sid\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1080,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/railwayreason.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1081,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/teenytinycellar.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1082,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/lovelydrum.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1083,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/hatefulrequest.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1084,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/aloofvest.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1085,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacidade.api.milvus.com.br\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1086,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"global.ketchcdn.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1087,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"e-i.com/SITW\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1088,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pleasantpump.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1089,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fearlessfaucet.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1090,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"unpkg.com/orejime\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1091,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/superficialeyes.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1092,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cassiecloud.com/loader.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1093,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/childlikeform.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1094,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"baycloud.com/tgcl.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1095,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/chickensstation.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1096,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/handsomelyhealth.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1097,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"s2.getsitecontrol.com/widgets\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1098,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"prismamediadigital.com/cnil.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1099,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.efilli.com/efl.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1100,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"api.brookiebot.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1101,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tiqcdn.com/utag/tui/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1102,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/basketballbelieve.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1103,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/colossalchance.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1104,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/aliasanvil.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1105,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"api.byscuit.com/data/client\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1106,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.lawwwing.com/widgets\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1107,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/hallowedinvention.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1108,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"pcz.pl/static/js/moo-cooker.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1109,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eucd/eucd.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1110,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cinabre.sudoc.abes.fr/psi_gui/js/bandeau.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1111,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/euopties.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1112,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cknotiz.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1113,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/common/disclaimer/load.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1114,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wpjslib-chunk-notification.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1115,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cpb.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1116,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.tagsOptOut.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1117,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"app.termly.io/embed.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1118,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"static.wpolityce.pl/rhododendron/js/terms_of_service.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1119,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/info_cook.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1120,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/datenschutz.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1121,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"c.sd1.fr/cn/cn.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1122,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"zgody.infor.pl/build/assets/js/main.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1123,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"static.s-sfr.fr/stats/sbtF.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1124,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dsgvo_2018.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1125,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/dsgvo.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1126,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"partner.vxcp.de/_js/vxcp_Common.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1127,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc.js?renew=false&referer=www.rs2.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1128,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/twcdisclaimer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1129,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jqueryCL.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1130,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/trustArcHelper.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1131,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"widget.clym.io/clym.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1132,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"htmedia.in/analytics-js/dap.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1133,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ufti/uftiLoader.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1134,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/includes/pltk/pltk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1135,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/onstuimig-tag-manager/base/adf-tm-base-min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1136,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/GDRP_banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1137,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ads/rgpd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1138,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"samtykker.agm.as/agent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1139,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"samtykker.agdermedia.no/agent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1140,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/PopUpPriva/PopPrivacymin.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1141,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"dialogue.sp-prod.net/messagingWithoutDetection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"excludedInitiatorDomains\": [\"globalplayer.com\"]\n    }\n  },\n  {\n    \"id\": 1142,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/datenschutz.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1143,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccm19.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1144,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/app.dsgvo.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1145,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/trigoAboveBox.jquery.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1146,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dsgvoinit.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1147,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/styleguide/mxqasqco.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1148,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tvp-tcfapi.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1149,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ccm19.de/app/public/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1150,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ccm.ceasy.de/public/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1151,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/orejime/js/orejime.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1152,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccm/public/app.js?apiKey\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1153,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ccm19.de/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1154,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wwwschutz.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1155,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccm19/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1156,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dsvgobanner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1157,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wecoma-lite.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1158,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"resourcesurw.azureedge.net/js/cc55.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1159,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccnst/ccbundle.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1160,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cc.mpa-web.de/public/app.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1161,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/klaro/klaro-pe.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1162,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"klaro-no-css.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1163,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ionic-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1164,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ckpl-webc.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1165,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"wagtail_tag_manager/wtm.bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1166,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"www.capitol.fr/streaming/cnil/cnil.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1167,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Dock/DockContent/Cards/GDPRCard/index.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1168,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"nanoGDPR.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1169,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"newsletters.ftv-preprod.fr/cnil/js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1170,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/components/es6/PrivacyPolicy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1171,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"js.hs-analytics.net/analytics\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1172,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jsdelivr.net/wp/wp-slimstat\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1173,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/dsvgo.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1174,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/GDPR/GDPR.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1175,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"htmedia.in/analytics-js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1176,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/rgpd/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1177,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/avia-bootstrap/js/klaro/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1178,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/tarteaucitron\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1179,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pandectes-core.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1180,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/orejime.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1181,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"jsdelivr.net/npm/cookify\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1182,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cbgCConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1183,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ckpl-webc.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1184,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/klaro-no-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1185,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tcfapi/tcfapi.umd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1186,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"mein.clickskeks.at/app.js?apiKey\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"]\n    }\n  },\n  {\n    \"id\": 1187,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consumer/cookie/client\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bt.com\"]\n    }\n  },\n  {\n    \"id\": 1188,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/frontend/ajax/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"romstal.ro\"]\n    }\n  },\n  {\n    \"id\": 1189,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rodo-agreement-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gremimedia.pl\"]\n    }\n  },\n  {\n    \"id\": 1190,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rodo-agreement-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zw.com.pl\"]\n    }\n  },\n  {\n    \"id\": 1191,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gtm.js?id=GTM-TCT2RJ\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fullrate.dk\"]\n    }\n  },\n  {\n    \"id\": 1192,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"paruvendu.fr\"]\n    }\n  },\n  {\n    \"id\": 1193,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"datenschutz.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wumpus-gollum-forum.de\"]\n    }\n  },\n  {\n    \"id\": 1194,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bho_infobar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ruecken-zentrum.de\"]\n    }\n  },\n  {\n    \"id\": 1195,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent/message.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ksta.de\"]\n    }\n  },\n  {\n    \"id\": 1196,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.truste.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"oracle.com\"]\n    }\n  },\n  {\n    \"id\": 1197,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cui-cookie-policy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"congstar.de\"]\n    }\n  },\n  {\n    \"id\": 1198,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bitly.com\"]\n    }\n  },\n  {\n    \"id\": 1199,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy/popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"cdaction.pl\"]\n    }\n  },\n  {\n    \"id\": 1200,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"knaufdoehetzelf.nl\"]\n    }\n  },\n  {\n    \"id\": 1201,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"louisvuitton.com\"]\n    }\n  },\n  {\n    \"id\": 1202,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/spmsg_addetection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tvspielfilm.de\"]\n    }\n  },\n  {\n    \"id\": 1203,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/firebox-gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"thelittleboxoffice.com\"]\n    }\n  },\n  {\n    \"id\": 1204,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.css\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"scholieren.com\"]\n    }\n  },\n  {\n    \"id\": 1205,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiescript.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"basiszinssatz.de\"]\n    }\n  },\n  {\n    \"id\": 1206,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies/bundle\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lucozadeenergy.com\"]\n    }\n  },\n  {\n    \"id\": 1207,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"guitarbackingtrack.com\"]\n    }\n  },\n  {\n    \"id\": 1208,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ip-consent.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"weersverwachting.nl\"]\n    }\n  },\n  {\n    \"id\": 1209,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiebot.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fluke.com\"]\n    }\n  },\n  {\n    \"id\": 1210,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tui-cookie-bar.html\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tuicruises.com\"]\n    }\n  },\n  {\n    \"id\": 1211,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-option.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"der-warnemuender.de\"]\n    }\n  },\n  {\n    \"id\": 1212,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"sexmarkt.nl\"]\n    }\n  },\n  {\n    \"id\": 1213,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ato-cookiebanner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"haag-streit.com\"]\n    }\n  },\n  {\n    \"id\": 1214,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"cummins.com\"]\n    }\n  },\n  {\n    \"id\": 1215,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/spd/spd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"woman.bg\"]\n    }\n  },\n  {\n    \"id\": 1216,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/spd/spd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"novini.bg\"]\n    }\n  },\n  {\n    \"id\": 1217,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/spd.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"sportal.bg\"]\n    }\n  },\n  {\n    \"id\": 1218,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieman.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"erfurter-bahn.de\"]\n    }\n  },\n  {\n    \"id\": 1219,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rg-gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lombardafiltri.it\"]\n    }\n  },\n  {\n    \"id\": 1220,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ibox.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aqualip.de\"]\n    }\n  },\n  {\n    \"id\": 1221,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cb-scripts\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"convertir-une-image.com\"]\n    }\n  },\n  {\n    \"id\": 1222,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.teraz.sk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"teraz.sk\"]\n    }\n  },\n  {\n    \"id\": 1223,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-policy.css\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"independent.com.mt\"]\n    }\n  },\n  {\n    \"id\": 1224,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/yello-cookie-layer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"yello.de\"]\n    }\n  },\n  {\n    \"id\": 1225,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"cashconverters.es\"]\n    }\n  },\n  {\n    \"id\": 1226,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/zsmessagebar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"christian.education\"]\n    }\n  },\n  {\n    \"id\": 1227,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pmc-pp-tou/privacy.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"rollingstone.com\"]\n    }\n  },\n  {\n    \"id\": 1228,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"outage.report\"]\n    }\n  },\n  {\n    \"id\": 1229,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/meWantCookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gamigo.de\"]\n    }\n  },\n  {\n    \"id\": 1230,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/meWantCookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gamigo.com\"]\n    }\n  },\n  {\n    \"id\": 1231,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bonniermag.se\"]\n    }\n  },\n  {\n    \"id\": 1232,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccpa/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lakesideparkmodels.com\"]\n    }\n  },\n  {\n    \"id\": 1233,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.umdaac.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"yoyogames.com\"]\n    }\n  },\n  {\n    \"id\": 1234,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/vendor/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"norma-connect.de\"]\n    }\n  },\n  {\n    \"id\": 1235,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookiePolicy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"test-aankoop.be\"]\n    }\n  },\n  {\n    \"id\": 1236,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"aliveachiever.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"dailydot.com\"]\n    }\n  },\n  {\n    \"id\": 1237,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-bar.salessquad.co.uk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"blochworld.com\"]\n    }\n  },\n  {\n    \"id\": 1238,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/coobann\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aida.de\"]\n    }\n  },\n  {\n    \"id\": 1239,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dsgvoCC.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"strabag-pfs.de\"]\n    }\n  },\n  {\n    \"id\": 1240,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.euck.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"avon-insurance.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1241,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/cookie-popup\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"simyo.nl\"]\n    }\n  },\n  {\n    \"id\": 1242,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bottom.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"herokuapp.com\"]\n    }\n  },\n  {\n    \"id\": 1243,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"melaniemartinezmusic.com\"]\n    }\n  },\n  {\n    \"id\": 1244,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cm-body.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tarnkappe.info\"]\n    }\n  },\n  {\n    \"id\": 1245,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/idgy_gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"metallumnovum.lt\"]\n    }\n  },\n  {\n    \"id\": 1246,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jw.cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"jacquelinewilson.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1247,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"iban-rechner.de\"]\n    }\n  },\n  {\n    \"id\": 1248,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/kameleoon.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"hanseaticbank.de\"]\n    }\n  },\n  {\n    \"id\": 1249,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"carriereonline.com\"]\n    }\n  },\n  {\n    \"id\": 1250,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eloqua\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"visma.fi\"]\n    }\n  },\n  {\n    \"id\": 1251,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eloqua\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"visma.no\"]\n    }\n  },\n  {\n    \"id\": 1252,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eloqua\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"visma.se\"]\n    }\n  },\n  {\n    \"id\": 1253,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eloqua\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"visma.nl\"]\n    }\n  },\n  {\n    \"id\": 1254,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eloqua\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"visma.com\"]\n    }\n  },\n  {\n    \"id\": 1255,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_banner_test.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"shinedown.com\"]\n    }\n  },\n  {\n    \"id\": 1256,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/streamify-gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"streamingbolaget.se\"]\n    }\n  },\n  {\n    \"id\": 1257,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies_alert.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lineadombra.it\"]\n    }\n  },\n  {\n    \"id\": 1258,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kidstaff.com.ua\"]\n    }\n  },\n  {\n    \"id\": 1259,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"catan.com\"]\n    }\n  },\n  {\n    \"id\": 1260,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"winbrok.es\"]\n    }\n  },\n  {\n    \"id\": 1261,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eurogdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"jcbeurope.eu\"]\n    }\n  },\n  {\n    \"id\": 1262,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ig_cookie_frontend\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"volksbund.de\"]\n    }\n  },\n  {\n    \"id\": 1263,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"bounceexchange.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lumberliquidators.com\"]\n    }\n  },\n  {\n    \"id\": 1264,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"keltendorf-mitterkirchen.at\"]\n    }\n  },\n  {\n    \"id\": 1265,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mitterkirchen.at\"]\n    }\n  },\n  {\n    \"id\": 1266,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vils.at\"]\n    }\n  },\n  {\n    \"id\": 1267,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tarteaucitron/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ratp.fr\"]\n    }\n  },\n  {\n    \"id\": 1268,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"CheckCookiePolicy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"remixshop.com\"]\n    }\n  },\n  {\n    \"id\": 1269,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/japfg/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"myfridgefood.com\"]\n    }\n  },\n  {\n    \"id\": 1270,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"raggal.at\"]\n    }\n  },\n  {\n    \"id\": 1271,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"haugsdorf.at\"]\n    }\n  },\n  {\n    \"id\": 1272,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"umhausen.at\"]\n    }\n  },\n  {\n    \"id\": 1273,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookienote/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wittetools.com\"]\n    }\n  },\n  {\n    \"id\": 1274,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr-idgy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vejutechnika.lt\"]\n    }\n  },\n  {\n    \"id\": 1275,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pltk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"goodcontent.pl\"]\n    }\n  },\n  {\n    \"id\": 1276,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tarteaucitron/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"arena-now.de\"]\n    }\n  },\n  {\n    \"id\": 1277,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/surbma-yes-no-popup/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bysarahkhan.com\"]\n    }\n  },\n  {\n    \"id\": 1278,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tryinteract.com\"]\n    }\n  },\n  {\n    \"id\": 1279,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consentMgmt/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"comdirect.de\"]\n    }\n  },\n  {\n    \"id\": 1280,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"pasztor.at\"]\n    }\n  },\n  {\n    \"id\": 1281,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tealium-external/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"weltverbesserer.de\"]\n    }\n  },\n  {\n    \"id\": 1282,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiesettings.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aarhusmotion.dk\"]\n    }\n  },\n  {\n    \"id\": 1283,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/oil\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tv2.dk\"]\n    }\n  },\n  {\n    \"id\": 1284,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieajx\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"goettgen.de\"]\n    }\n  },\n  {\n    \"id\": 1285,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.parkers.co.uk\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"parkers.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1286,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"lovelydrum.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"insidermonkey.com\"]\n    }\n  },\n  {\n    \"id\": 1287,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/intuCookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"intu.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1288,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tags.tiqcdn.com/utag\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"123-reg.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1289,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"stormyachiever.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"thewindowsclub.com\"]\n    }\n  },\n  {\n    \"id\": 1290,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gcb/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gallup.com\"]\n    }\n  },\n  {\n    \"id\": 1291,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/native-message\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wiwo.de\"]\n    }\n  },\n  {\n    \"id\": 1292,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"koekje.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"baustellenabsicherung24.de\"]\n    }\n  },\n  {\n    \"id\": 1293,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/getdisclaimer\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wikifolio.com\"]\n    }\n  },\n  {\n    \"id\": 1294,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/echonetcookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"muenchenticket.de\"]\n    }\n  },\n  {\n    \"id\": 1295,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies-anekis.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"premiosopenbank.com\"]\n    }\n  },\n  {\n    \"id\": 1296,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"latoisondor.com\"]\n    }\n  },\n  {\n    \"id\": 1297,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cbgCConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"carlsberggroup.com\"]\n    }\n  },\n  {\n    \"id\": 1298,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"jugendregion.at\"]\n    }\n  },\n  {\n    \"id\": 1299,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp-v2/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"youmath.it\"]\n    }\n  },\n  {\n    \"id\": 1300,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/permission/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"1und1.de\"]\n    }\n  },\n  {\n    \"id\": 1301,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"js.driftt.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zenroom.org\"]\n    }\n  },\n  {\n    \"id\": 1302,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/bwx-cookie-consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nosta.com\"]\n    }\n  },\n  {\n    \"id\": 1303,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie_settings\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aerotime.aero\"]\n    }\n  },\n  {\n    \"id\": 1304,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"stadt-bobingen.de\"]\n    }\n  },\n  {\n    \"id\": 1305,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"troubledtail.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"interestingengineering.com\"]\n    }\n  },\n  {\n    \"id\": 1306,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tracking.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"arsys.es\"]\n    }\n  },\n  {\n    \"id\": 1307,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tracking.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"arsys.fr\"]\n    }\n  },\n  {\n    \"id\": 1308,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tracking.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"arsys.pt\"]\n    }\n  },\n  {\n    \"id\": 1309,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tracking.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"arsys.net\"]\n    }\n  },\n  {\n    \"id\": 1310,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy/Bootstrap\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tescobank.com\"]\n    }\n  },\n  {\n    \"id\": 1311,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"adlkofen.de\"]\n    }\n  },\n  {\n    \"id\": 1312,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"klinikwersbach.de\"]\n    }\n  },\n  {\n    \"id\": 1313,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieWall/clb.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"innogy.pl\"]\n    }\n  },\n  {\n    \"id\": 1314,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"e-pages.dk\"]\n    }\n  },\n  {\n    \"id\": 1315,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/easycmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lagerhaus.at\"]\n    }\n  },\n  {\n    \"id\": 1316,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookielab/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ol.fr\"]\n    }\n  },\n  {\n    \"id\": 1317,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/thcookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"megadruck.de\"]\n    }\n  },\n  {\n    \"id\": 1318,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"catan.de\"]\n    }\n  },\n  {\n    \"id\": 1319,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"qcnet.com\"]\n    }\n  },\n  {\n    \"id\": 1320,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/msmCookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"moneysupermarket.com\"]\n    }\n  },\n  {\n    \"id\": 1321,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eon-com-tracking-consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"eon.com\"]\n    }\n  },\n  {\n    \"id\": 1322,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/website-cookie-preferences\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nationaltrust.org.uk\"]\n    }\n  },\n  {\n    \"id\": 1323,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-bundle\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zoopla.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1324,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-wall\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"xebia.com\"]\n    }\n  },\n  {\n    \"id\": 1325,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"dhl.com\"]\n    }\n  },\n  {\n    \"id\": 1326,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cp01.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"jzzo.com\"]\n    }\n  },\n  {\n    \"id\": 1327,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/klaro/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"salesviewer.com\"]\n    }\n  },\n  {\n    \"id\": 1328,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr.bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mediacourant.nl\"]\n    }\n  },\n  {\n    \"id\": 1329,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-management/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"plasteurope.com\"]\n    }\n  },\n  {\n    \"id\": 1330,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"dragnsurvey.com\"]\n    }\n  },\n  {\n    \"id\": 1331,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/snap-popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"maggiore.it\"]\n    }\n  },\n  {\n    \"id\": 1332,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/snap-popup.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"amicoblu.it\"]\n    }\n  },\n  {\n    \"id\": 1333,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/apprise\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"podrozerowerowe.info\"]\n    }\n  },\n  {\n    \"id\": 1334,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/data-consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"stadtwerke-luebz.de\"]\n    }\n  },\n  {\n    \"id\": 1335,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/neo-cookie-layer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"united-domains.de\"]\n    }\n  },\n  {\n    \"id\": 1336,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieWidget.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"peter-bringts.de\"]\n    }\n  },\n  {\n    \"id\": 1337,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lux-residence.com\"]\n    }\n  },\n  {\n    \"id\": 1338,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-manager-v\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"df.eu\"]\n    }\n  },\n  {\n    \"id\": 1339,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mobilejob.com\"]\n    }\n  },\n  {\n    \"id\": 1340,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"strato.de\"]\n    }\n  },\n  {\n    \"id\": 1341,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"strato.nl\"]\n    }\n  },\n  {\n    \"id\": 1342,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"strato.fr\"]\n    }\n  },\n  {\n    \"id\": 1343,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"strato.es\"]\n    }\n  },\n  {\n    \"id\": 1344,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"strato-hosting.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1345,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"renault.de\"]\n    }\n  },\n  {\n    \"id\": 1346,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/checkCookieConsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"andrewssykes.fr\"]\n    }\n  },\n  {\n    \"id\": 1347,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-manager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"cornelsen.de\"]\n    }\n  },\n  {\n    \"id\": 1348,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"synopsys.com\"]\n    }\n  },\n  {\n    \"id\": 1349,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"total.com\"]\n    }\n  },\n  {\n    \"id\": 1350,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tcf2.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"0calc.com\"]\n    }\n  },\n  {\n    \"id\": 1351,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tcf2.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"0rechner.de\"]\n    }\n  },\n  {\n    \"id\": 1352,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tcf2.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"0calc.fr\"]\n    }\n  },\n  {\n    \"id\": 1353,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gusto.at\"]\n    }\n  },\n  {\n    \"id\": 1354,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dsgvo-opt-in.css\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kosta.at\"]\n    }\n  },\n  {\n    \"id\": 1355,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"total.nl\"]\n    }\n  },\n  {\n    \"id\": 1356,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dywc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"radioschwaben.de\"]\n    }\n  },\n  {\n    \"id\": 1357,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/microsite-consent-disclaimer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lyxoretf.com\"]\n    }\n  },\n  {\n    \"id\": 1358,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/eea\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"psychcentral.com\"]\n    }\n  },\n  {\n    \"id\": 1359,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sm-policy-banner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"storage-mart.com\"]\n    }\n  },\n  {\n    \"id\": 1360,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/unitb-cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"juedische-allgemeine.de\"]\n    }\n  },\n  {\n    \"id\": 1361,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"casanetwork.hu\"]\n    }\n  },\n  {\n    \"id\": 1362,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/legal/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kamkabel.ru\"]\n    }\n  },\n  {\n    \"id\": 1363,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/accept.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"oreluniver.ru\"]\n    }\n  },\n  {\n    \"id\": 1364,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/doria.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lactease.com\"]\n    }\n  },\n  {\n    \"id\": 1365,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"js.driftt.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ikonltd.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1366,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/esb-privacy.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"erstebank.hr\"]\n    }\n  },\n  {\n    \"id\": 1367,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mediamus-cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"swl-unser-stadtwerk.de\"]\n    }\n  },\n  {\n    \"id\": 1368,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/vinegar.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"stadtwerke-herne.de\"]\n    }\n  },\n  {\n    \"id\": 1369,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/vhs-assets-cookie-control-js.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"planet-beruf.de\"]\n    }\n  },\n  {\n    \"id\": 1370,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ob_rgpd/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"orangebank.fr\"]\n    }\n  },\n  {\n    \"id\": 1371,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mintos.com\"]\n    }\n  },\n  {\n    \"id\": 1372,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/syno_cookie_element\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"synology.com\"]\n    }\n  },\n  {\n    \"id\": 1373,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jcookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"javhd.com\"]\n    }\n  },\n  {\n    \"id\": 1374,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"sparkasse.at\"]\n    }\n  },\n  {\n    \"id\": 1375,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/PoliticaCookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fincaraiz.com.co\"]\n    }\n  },\n  {\n    \"id\": 1376,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Team23_SimpleCookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kleiderhelden.com\"]\n    }\n  },\n  {\n    \"id\": 1377,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"esprit.de\"]\n    }\n  },\n  {\n    \"id\": 1378,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieCutter\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"thebureauinvestigates.com\"]\n    }\n  },\n  {\n    \"id\": 1379,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ing.es\"]\n    }\n  },\n  {\n    \"id\": 1380,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tenable.com\"]\n    }\n  },\n  {\n    \"id\": 1381,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"thetrainline.com\"]\n    }\n  },\n  {\n    \"id\": 1382,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"op.fi\"]\n    }\n  },\n  {\n    \"id\": 1383,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.bundle.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fastbill.com\"]\n    }\n  },\n  {\n    \"id\": 1384,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gaming-style.com\"]\n    }\n  },\n  {\n    \"id\": 1385,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"transparenzregister.de\"]\n    }\n  },\n  {\n    \"id\": 1386,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/uc/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mcmakler.de\"]\n    }\n  },\n  {\n    \"id\": 1387,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-policy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ubuntu.com\"]\n    }\n  },\n  {\n    \"id\": 1388,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zitatezumnachdenken.com\"]\n    }\n  },\n  {\n    \"id\": 1389,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"metoffice.gov.uk\"]\n    }\n  },\n  {\n    \"id\": 1390,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"churchofengland.org\"]\n    }\n  },\n  {\n    \"id\": 1391,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cnst.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tauschticket.de\"]\n    }\n  },\n  {\n    \"id\": 1392,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"liberbank.es\"]\n    }\n  },\n  {\n    \"id\": 1393,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/iCookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"h-supertools.com\"]\n    }\n  },\n  {\n    \"id\": 1394,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/he-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"dennree.de\"]\n    }\n  },\n  {\n    \"id\": 1395,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/uc_cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gastro24.de\"]\n    }\n  },\n  {\n    \"id\": 1396,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bopla.de\"]\n    }\n  },\n  {\n    \"id\": 1397,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"telerama.fr\"]\n    }\n  },\n  {\n    \"id\": 1398,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"courrierinternational.com\"]\n    }\n  },\n  {\n    \"id\": 1399,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vodkaster.com\"]\n    }\n  },\n  {\n    \"id\": 1400,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"brsimg.com/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"boursorama.com\"]\n    }\n  },\n  {\n    \"id\": 1401,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"trcking.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"canalplus.com\"]\n    }\n  },\n  {\n    \"id\": 1402,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"hauptbahnhofcity.wien\"]\n    }\n  },\n  {\n    \"id\": 1403,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"pons.com\"]\n    }\n  },\n  {\n    \"id\": 1404,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiefly/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"internet-rockstars.com\"]\n    }\n  },\n  {\n    \"id\": 1405,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"stepstone.de\"]\n    }\n  },\n  {\n    \"id\": 1406,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"stepstone.at\"]\n    }\n  },\n  {\n    \"id\": 1407,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"santander.pl\"]\n    }\n  },\n  {\n    \"id\": 1408,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"trcking.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"canal-plus.com\"]\n    }\n  },\n  {\n    \"id\": 1409,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aftenposten.no\"]\n    }\n  },\n  {\n    \"id\": 1410,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"coca-cola-deutschland.de\"]\n    }\n  },\n  {\n    \"id\": 1411,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-layer/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bahn.de\"]\n    }\n  },\n  {\n    \"id\": 1412,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"yodel.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1413,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"digit-photo.com\"]\n    }\n  },\n  {\n    \"id\": 1414,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/checkcookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"iracing.com\"]\n    }\n  },\n  {\n    \"id\": 1415,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"avast.com\"]\n    }\n  },\n  {\n    \"id\": 1416,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vg.no\"]\n    }\n  },\n  {\n    \"id\": 1417,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tek.no\"]\n    }\n  },\n  {\n    \"id\": 1418,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"finn.no\"]\n    }\n  },\n  {\n    \"id\": 1419,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lifesycle.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1420,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"next-immo.com\"]\n    }\n  },\n  {\n    \"id\": 1421,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fnGdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"financer.com\"]\n    }\n  },\n  {\n    \"id\": 1422,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-layer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mercedes-benz.io\"]\n    }\n  },\n  {\n    \"id\": 1423,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/messagingNoTcfApi.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"svd.se\"]\n    }\n  },\n  {\n    \"id\": 1424,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/sygnal42-gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"blitzrechner.de\"]\n    }\n  },\n  {\n    \"id\": 1425,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieLayer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"multipower.com\"]\n    }\n  },\n  {\n    \"id\": 1426,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_management.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"meteoradar.ch\"]\n    }\n  },\n  {\n    \"id\": 1427,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-preferences\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"halebop.se\"]\n    }\n  },\n  {\n    \"id\": 1428,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nif.no\"]\n    }\n  },\n  {\n    \"id\": 1429,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/klaro-no-css.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"frag-einen-anwalt.de\"]\n    }\n  },\n  {\n    \"id\": 1430,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"stema.de\"]\n    }\n  },\n  {\n    \"id\": 1431,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieBox.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vinos.de\"]\n    }\n  },\n  {\n    \"id\": 1432,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"swisscom.ch\"]\n    }\n  },\n  {\n    \"id\": 1433,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"schwaebische.de\"]\n    }\n  },\n  {\n    \"id\": 1434,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"guldsmykket.dk\"]\n    }\n  },\n  {\n    \"id\": 1435,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mieterengel.de\"]\n    }\n  },\n  {\n    \"id\": 1436,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/uc_cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gastro-hero.de\"]\n    }\n  },\n  {\n    \"id\": 1437,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kurnik.pl\"]\n    }\n  },\n  {\n    \"id\": 1438,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dg-governance/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nytimes.com\"]\n    }\n  },\n  {\n    \"id\": 1439,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dg-governance/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"inyt.com\"]\n    }\n  },\n  {\n    \"id\": 1440,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zukunftsheizen.de\"]\n    }\n  },\n  {\n    \"id\": 1441,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"truphone.com\"]\n    }\n  },\n  {\n    \"id\": 1442,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"oka.be\"]\n    }\n  },\n  {\n    \"id\": 1443,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"prada.com\"]\n    }\n  },\n  {\n    \"id\": 1444,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"online-spellcheck.com\"]\n    }\n  },\n  {\n    \"id\": 1445,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/it-cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"geze.de\"]\n    }\n  },\n  {\n    \"id\": 1446,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/it-cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"geze.pl\"]\n    }\n  },\n  {\n    \"id\": 1447,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ConsentInit.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"smartphonehoesjes.nl\"]\n    }\n  },\n  {\n    \"id\": 1448,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieser.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aco.com.pl\"]\n    }\n  },\n  {\n    \"id\": 1449,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bluelightcard.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1450,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/vfConsentCookiesCs.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vodafone.cz\"]\n    }\n  },\n  {\n    \"id\": 1451,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bt.no\"]\n    }\n  },\n  {\n    \"id\": 1452,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nwb-jobboerse.de\"]\n    }\n  },\n  {\n    \"id\": 1453,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/almacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ampparit.com\"]\n    }\n  },\n  {\n    \"id\": 1454,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"avg.com\"]\n    }\n  },\n  {\n    \"id\": 1455,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"washingtonpost.com\"]\n    }\n  },\n  {\n    \"id\": 1456,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aftenbladet.no\"]\n    }\n  },\n  {\n    \"id\": 1457,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_flyout.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"festo.com\"]\n    }\n  },\n  {\n    \"id\": 1458,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/klaro.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"beckhoff.com\"]\n    }\n  },\n  {\n    \"id\": 1459,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Manolo_CookieConsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"manoloblahnik.com\"]\n    }\n  },\n  {\n    \"id\": 1460,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/LdCookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"landkreis-eichstaett.de\"]\n    }\n  },\n  {\n    \"id\": 1461,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/legal-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"meineapotheke.de\"]\n    }\n  },\n  {\n    \"id\": 1462,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"inishpharmacy.com\"]\n    }\n  },\n  {\n    \"id\": 1463,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-main\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"godaddy.com\"]\n    }\n  },\n  {\n    \"id\": 1464,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ultraleicht-trekking.com\"]\n    }\n  },\n  {\n    \"id\": 1465,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/base-nf.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lecanardenchaine.fr\"]\n    }\n  },\n  {\n    \"id\": 1466,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"toestemmingen.snp.nl\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"snp.nl\"]\n    }\n  },\n  {\n    \"id\": 1467,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mendeley.com\"]\n    }\n  },\n  {\n    \"id\": 1468,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cm/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"howatherm.de\"]\n    }\n  },\n  {\n    \"id\": 1469,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/osb-cmp.min.mjs\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tvgids.nl\"]\n    }\n  },\n  {\n    \"id\": 1470,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"01net.com\"]\n    }\n  },\n  {\n    \"id\": 1471,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.maerkischekiste.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"maerkischekiste.de\"]\n    }\n  },\n  {\n    \"id\": 1472,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"coca-cola.pl\"]\n    }\n  },\n  {\n    \"id\": 1473,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"coca-cola.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1474,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"coca-cola.dk\"]\n    }\n  },\n  {\n    \"id\": 1475,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/it_nsc.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nonsolocap.it\"]\n    }\n  },\n  {\n    \"id\": 1476,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"totalwar.com\"]\n    }\n  },\n  {\n    \"id\": 1477,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gameswelt.ch\"]\n    }\n  },\n  {\n    \"id\": 1478,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gameswelt.de\"]\n    }\n  },\n  {\n    \"id\": 1479,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gameswelt.at\"]\n    }\n  },\n  {\n    \"id\": 1480,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"h1.versicherungsjournal.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"versicherungsjournal.de\"]\n    }\n  },\n  {\n    \"id\": 1481,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"topannonces.fr\"]\n    }\n  },\n  {\n    \"id\": 1482,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ellisphere.fr\"]\n    }\n  },\n  {\n    \"id\": 1483,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mdv.cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"biehler-cycling.com\"]\n    }\n  },\n  {\n    \"id\": 1484,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"here.com\"]\n    }\n  },\n  {\n    \"id\": 1485,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gizchina.com\"]\n    }\n  },\n  {\n    \"id\": 1486,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"consorsbank.de\"]\n    }\n  },\n  {\n    \"id\": 1487,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/alert-info.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"asus.com\"]\n    }\n  },\n  {\n    \"id\": 1488,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/alert-info_cn.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"asus.com.cn\"]\n    }\n  },\n  {\n    \"id\": 1489,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/almacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"etuovi.com\"]\n    }\n  },\n  {\n    \"id\": 1490,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/almacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vuokraovi.com\"]\n    }\n  },\n  {\n    \"id\": 1491,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/almacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"arvopaperi.fi\"]\n    }\n  },\n  {\n    \"id\": 1492,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp.genesis\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vodafone.de\"]\n    }\n  },\n  {\n    \"id\": 1493,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/newCookieChoice.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vape-phone.fr\"]\n    }\n  },\n  {\n    \"id\": 1494,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-settings-manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"aok.de\"]\n    }\n  },\n  {\n    \"id\": 1495,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Ronis_Cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"slaters.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1496,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Hmm24_Cookiebanner/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zaehlerschrank24.de\"]\n    }\n  },\n  {\n    \"id\": 1497,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consensu.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"shinden.pl\"]\n    }\n  },\n  {\n    \"id\": 1498,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"js_privacy_cmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"e24.no\"]\n    }\n  },\n  {\n    \"id\": 1499,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieThough.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wochenblitz.com\"]\n    }\n  },\n  {\n    \"id\": 1500,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"beethoven.de\"]\n    }\n  },\n  {\n    \"id\": 1501,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieWall/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"eon.pl\"]\n    }\n  },\n  {\n    \"id\": 1502,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"technics.com\"]\n    }\n  },\n  {\n    \"id\": 1503,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookiebanner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"unibo.it\"]\n    }\n  },\n  {\n    \"id\": 1504,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"guloggratis.dk\"]\n    }\n  },\n  {\n    \"id\": 1505,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"trustarc.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"avantiwestcoast.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1506,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"stickypassword.com\"]\n    }\n  },\n  {\n    \"id\": 1507,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_layer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"billiger-mietwagen.de\"]\n    }\n  },\n  {\n    \"id\": 1508,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/c22.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"cd.cz\"]\n    }\n  },\n  {\n    \"id\": 1509,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"o2.cz\"]\n    }\n  },\n  {\n    \"id\": 1510,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tarteaucitron/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"flaine.com\"]\n    }\n  },\n  {\n    \"id\": 1511,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/almacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nettikaravaani.com\"]\n    }\n  },\n  {\n    \"id\": 1512,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/stylecc\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"immozentral.com\"]\n    }\n  },\n  {\n    \"id\": 1513,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieConsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"loesdau.de\"]\n    }\n  },\n  {\n    \"id\": 1514,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zugtouren.de\"]\n    }\n  },\n  {\n    \"id\": 1515,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"t-mobile.cz\"]\n    }\n  },\n  {\n    \"id\": 1516,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gfps.com\"]\n    }\n  },\n  {\n    \"id\": 1517,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jquery.cookiekit.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"dgeg.gov.pt\"]\n    }\n  },\n  {\n    \"id\": 1518,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"minmote.no\"]\n    }\n  },\n  {\n    \"id\": 1519,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tc_privacy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"matmut.fr\"]\n    }\n  },\n  {\n    \"id\": 1520,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie_modal/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"shirtee.com\"]\n    }\n  },\n  {\n    \"id\": 1521,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/borlabs-cookie\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"finance-magazin.de\"]\n    }\n  },\n  {\n    \"id\": 1522,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"elle.se\"]\n    }\n  },\n  {\n    \"id\": 1523,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fyens.dk\"]\n    }\n  },\n  {\n    \"id\": 1524,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cf-analytics/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"chatfuel.com\"]\n    }\n  },\n  {\n    \"id\": 1525,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cc.labu24.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"labu24.de\"]\n    }\n  },\n  {\n    \"id\": 1526,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.ptj.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ptj.de\"]\n    }\n  },\n  {\n    \"id\": 1527,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.autopflege24.net\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"autopflege24.net\"]\n    }\n  },\n  {\n    \"id\": 1528,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"natune.net\"]\n    }\n  },\n  {\n    \"id\": 1529,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-banner\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"studio3t.com\"]\n    }\n  },\n  {\n    \"id\": 1530,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies_2020\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"pensionlisboa.com\"]\n    }\n  },\n  {\n    \"id\": 1531,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ensighten/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"britishairways.com\"]\n    }\n  },\n  {\n    \"id\": 1532,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ensighten/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ba.com\"]\n    }\n  },\n  {\n    \"id\": 1533,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"finanz-forum.de\"]\n    }\n  },\n  {\n    \"id\": 1534,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"konsultacjejst.pl\"]\n    }\n  },\n  {\n    \"id\": 1535,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/klaro\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tarif4you.de\"]\n    }\n  },\n  {\n    \"id\": 1536,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ccm.carpassion.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"carpassion.com\"]\n    }\n  },\n  {\n    \"id\": 1537,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"godt.no\"]\n    }\n  },\n  {\n    \"id\": 1538,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"pent.no\"]\n    }\n  },\n  {\n    \"id\": 1539,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"rtr.at\"]\n    }\n  },\n  {\n    \"id\": 1540,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-policy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"hotmart.com\"]\n    }\n  },\n  {\n    \"id\": 1541,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"widforss.no\"]\n    }\n  },\n  {\n    \"id\": 1542,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/acmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ameblo.jp\"]\n    }\n  },\n  {\n    \"id\": 1543,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"regentcentre.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1544,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dkmb_gdpr.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fightful.com\"]\n    }\n  },\n  {\n    \"id\": 1545,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pandectes-core.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"arktis.de\"]\n    }\n  },\n  {\n    \"id\": 1546,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pandectes-core.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"apfelband.de\"]\n    }\n  },\n  {\n    \"id\": 1547,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/pandectes-core.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"schvarz.com\"]\n    }\n  },\n  {\n    \"id\": 1548,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/uc-cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"santander.de\"]\n    }\n  },\n  {\n    \"id\": 1549,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookpop.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"electromenager-compare.com\"]\n    }\n  },\n  {\n    \"id\": 1550,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"klart.se\"]\n    }\n  },\n  {\n    \"id\": 1551,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tv.nu\"]\n    }\n  },\n  {\n    \"id\": 1552,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"hemnet.se\"]\n    }\n  },\n  {\n    \"id\": 1553,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/aw-cookie.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"soprema.fr\"]\n    }\n  },\n  {\n    \"id\": 1554,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/fc_cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bosch-stiftung.de\"]\n    }\n  },\n  {\n    \"id\": 1555,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zurich.it\"]\n    }\n  },\n  {\n    \"id\": 1556,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.lavie.fr\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lavie.fr\"]\n    }\n  },\n  {\n    \"id\": 1557,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"twisto.pl\"]\n    }\n  },\n  {\n    \"id\": 1558,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"koestritzer.de\"]\n    }\n  },\n  {\n    \"id\": 1559,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiestarter\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"postfinance.ch\"]\n    }\n  },\n  {\n    \"id\": 1560,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/b2c.cookie-consent@latest/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"santanderconsumer.se\"]\n    }\n  },\n  {\n    \"id\": 1561,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/melindres/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"televes.com\"]\n    }\n  },\n  {\n    \"id\": 1562,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gdpr/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"broedersgezondheidswinkel.nl\"]\n    }\n  },\n  {\n    \"id\": 1563,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/DisclaimerControl.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"vontobel.com\"]\n    }\n  },\n  {\n    \"id\": 1564,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/gtcookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fixtout.fr\"]\n    }\n  },\n  {\n    \"id\": 1565,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"le-jackpot-des-medailles-safti.fr\"]\n    }\n  },\n  {\n    \"id\": 1566,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/RR_KE_ccm19/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kefalonia-griechenland.com\"]\n    }\n  },\n  {\n    \"id\": 1567,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/udg-uc-sdk.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"porsche.com\"]\n    }\n  },\n  {\n    \"id\": 1568,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mann.tv\"]\n    }\n  },\n  {\n    \"id\": 1569,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-page.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wz.de\"]\n    }\n  },\n  {\n    \"id\": 1570,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tarteaucitron.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"intex.fr\"]\n    }\n  },\n  {\n    \"id\": 1571,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mtb-news.de\"]\n    }\n  },\n  {\n    \"id\": 1572,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"rennrad-news.de\"]\n    }\n  },\n  {\n    \"id\": 1573,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"emtb-news.de\"]\n    }\n  },\n  {\n    \"id\": 1574,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"cyberghostvpn.com\"]\n    }\n  },\n  {\n    \"id\": 1575,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"playok.com\"]\n    }\n  },\n  {\n    \"id\": 1576,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"api.usercentrics.eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ing.de\"]\n    }\n  },\n  {\n    \"id\": 1577,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/iceCookie.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mamacar.cz\"]\n    }\n  },\n  {\n    \"id\": 1578,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/otBannerSdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"springerprofessional.de\"]\n    }\n  },\n  {\n    \"id\": 1579,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tui.se\"]\n    }\n  },\n  {\n    \"id\": 1580,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tui.fi\"]\n    }\n  },\n  {\n    \"id\": 1581,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/index.php?consent_manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"papierkram.de\"]\n    }\n  },\n  {\n    \"id\": 1582,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/user-consent-management/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"americanexpress.com\"]\n    }\n  },\n  {\n    \"id\": 1583,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"privatebanking.hsbc.com\"]\n    }\n  },\n  {\n    \"id\": 1584,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"hsbc.pl\"]\n    }\n  },\n  {\n    \"id\": 1585,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/smedia_cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"renzgroup.de\"]\n    }\n  },\n  {\n    \"id\": 1586,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.tanke-guenstig.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tanke-guenstig.de\"]\n    }\n  },\n  {\n    \"id\": 1587,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"histoire-et-civilisations.com\"]\n    }\n  },\n  {\n    \"id\": 1588,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/mfe-cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"santander.com.br\"]\n    }\n  },\n  {\n    \"id\": 1589,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ccm1.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"dlr.de\"]\n    }\n  },\n  {\n    \"id\": 1590,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent-manager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"pdf24.org\"]\n    }\n  },\n  {\n    \"id\": 1591,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lawgazette.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1592,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"ccm.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"abload.de\"]\n    }\n  },\n  {\n    \"id\": 1593,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies_utils.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"uca.es\"]\n    }\n  },\n  {\n    \"id\": 1594,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookie-overlay.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bensanitair.nl\"]\n    }\n  },\n  {\n    \"id\": 1595,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/boldCookie_custom.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"investice.cz\"]\n    }\n  },\n  {\n    \"id\": 1596,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmapp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ccm19.de\"]\n    }\n  },\n  {\n    \"id\": 1597,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cdn.civiccomputing.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gfms.com\"]\n    }\n  },\n  {\n    \"id\": 1598,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ppp/js/permission-client\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"meinaccount.gmx.net\"]\n    }\n  },\n  {\n    \"id\": 1599,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ppp/js/permission-client\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"hilfe.gmx.net\"]\n    }\n  },\n  {\n    \"id\": 1600,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ppp/js/permission-client\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mein.web.de\"]\n    }\n  },\n  {\n    \"id\": 1601,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"tags.tiqcdn.com/utag\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lineadirecta.com\"]\n    }\n  },\n  {\n    \"id\": 1602,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ujam_tracking/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ujam.com\"]\n    }\n  },\n  {\n    \"id\": 1603,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ugp-api/webcontent/v1/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ae.com\"]\n    }\n  },\n  {\n    \"id\": 1604,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"jobsireland.ie\"]\n    }\n  },\n  {\n    \"id\": 1605,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"app.usercentrics.eu\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mainpost.de\"]\n    }\n  },\n  {\n    \"id\": 1606,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"pdcookiepro//views/js/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"czasnaherbate.net\"]\n    }\n  },\n  {\n    \"id\": 1607,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dtc-fe/policy-control\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"zs-watch.com\"]\n    }\n  },\n  {\n    \"id\": 1608,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"loveholidays.ie\"]\n    }\n  },\n  {\n    \"id\": 1609,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/inc/cookie_modal_ajax.php\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"georg.at\"]\n    }\n  },\n  {\n    \"id\": 1610,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/_next/static/chunks/cookieOverlay\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"galaxus.fr\"]\n    }\n  },\n  {\n    \"id\": 1611,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.werner-mertz.de\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"werner-mertz.de\"]\n    }\n  },\n  {\n    \"id\": 1612,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/dist/js/cmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"corporate.modivo.com\"]\n    }\n  },\n  {\n    \"id\": 1613,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/jimmsconsent\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"jimms.fi\"]\n    }\n  },\n  {\n    \"id\": 1614,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"chunk-cookie-consent-modal\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"jobup.ch\"]\n    }\n  },\n  {\n    \"id\": 1615,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Cookie/ccm19/public/index.php/app.js?\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"condair.de\"]\n    }\n  },\n  {\n    \"id\": 1616,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"eurostar.com\"]\n    }\n  },\n  {\n    \"id\": 1617,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/static/ct/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wko.at\"]\n    }\n  },\n  {\n    \"id\": 1618,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ui/common/scripts/cookies/cookieModalComponent-797ec8a07a.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"oem.no\"]\n    }\n  },\n  {\n    \"id\": 1619,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"politico.eu\"]\n    }\n  },\n  {\n    \"id\": 1620,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lovelybooks.de\"]\n    }\n  },\n  {\n    \"id\": 1621,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"idealo.de\"]\n    }\n  },\n  {\n    \"id\": 1622,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"idealo.at\"]\n    }\n  },\n  {\n    \"id\": 1623,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"idealo.es\"]\n    }\n  },\n  {\n    \"id\": 1624,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"idealo.fr\"]\n    }\n  },\n  {\n    \"id\": 1625,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"idealo.it\"]\n    }\n  },\n  {\n    \"id\": 1626,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"idealo.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1627,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"femmeactuelle.fr\"]\n    }\n  },\n  {\n    \"id\": 1628,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"hausjournal.net\"]\n    }\n  },\n  {\n    \"id\": 1629,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"lepoint.fr\"]\n    }\n  },\n  {\n    \"id\": 1630,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"chefkoch.de\"]\n    }\n  },\n  {\n    \"id\": 1631,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"boerse.de\"]\n    }\n  },\n  {\n    \"id\": 1632,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"liberation.fr\"]\n    }\n  },\n  {\n    \"id\": 1633,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"caradisiac.com\"]\n    }\n  },\n  {\n    \"id\": 1634,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wrapperMessagingWithoutDetection\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"anderes-wort.de\"]\n    }\n  },\n  {\n    \"id\": 1635,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CookieBanner.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bauer-baumschulen.ch\"]\n    }\n  },\n  {\n    \"id\": 1636,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"strato.se\"]\n    }\n  },\n  {\n    \"id\": 1637,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"imusic.de\"]\n    }\n  },\n  {\n    \"id\": 1638,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"imusic.dk\"]\n    }\n  },\n  {\n    \"id\": 1639,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"imusic.no\"]\n    }\n  },\n  {\n    \"id\": 1640,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"imusic.se\"]\n    }\n  },\n  {\n    \"id\": 1641,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cc.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"imusic.co\"]\n    }\n  },\n  {\n    \"id\": 1642,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"comparethemarket.com\"]\n    }\n  },\n  {\n    \"id\": 1643,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"malservice.aftonbladet.se\"]\n    }\n  },\n  {\n    \"id\": 1644,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/brabo-cookie/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"brandpreventiewinkel.nl\"]\n    }\n  },\n  {\n    \"id\": 1645,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/almacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nettimoto.com\"]\n    }\n  },\n  {\n    \"id\": 1646,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"futura-sciences.com\"]\n    }\n  },\n  {\n    \"id\": 1647,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-box\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"schmalz.com\"]\n    }\n  },\n  {\n    \"id\": 1648,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/popupConsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"the-fence.com\"]\n    }\n  },\n  {\n    \"id\": 1649,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-files/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"paradoxwikis.com\"]\n    }\n  },\n  {\n    \"id\": 1650,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookieconsent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"expert.es\"]\n    }\n  },\n  {\n    \"id\": 1651,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.quantcast.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gettr.com\"]\n    }\n  },\n  {\n    \"id\": 1652,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"coco.we-online.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"we-online.com\"]\n    }\n  },\n  {\n    \"id\": 1653,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/modulos/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"villagrancanaria.com\"]\n    }\n  },\n  {\n    \"id\": 1654,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"carhartt.com\"]\n    }\n  },\n  {\n    \"id\": 1655,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/almacmp\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kotikokki.net\"]\n    }\n  },\n  {\n    \"id\": 1656,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rcs_cpmt/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"open.online\"]\n    }\n  },\n  {\n    \"id\": 1657,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rcs_cpmt/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"style.corriere.it\"]\n    }\n  },\n  {\n    \"id\": 1658,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ph-cookie-helper-mu/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"uwartsonline.nl\"]\n    }\n  },\n  {\n    \"id\": 1659,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \".consent-\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"pcgames.de\"]\n    }\n  },\n  {\n    \"id\": 1660,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.quantcast.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"deviantart.com\"]\n    }\n  },\n  {\n    \"id\": 1661,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"omni.se\"]\n    }\n  },\n  {\n    \"id\": 1662,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Libs/cookies\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nadeta.cz\"]\n    }\n  },\n  {\n    \"id\": 1663,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/standard-cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"standard.sk\"]\n    }\n  },\n  {\n    \"id\": 1664,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Disclaimer.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"private.com\"]\n    }\n  },\n  {\n    \"id\": 1665,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"seeandso.com\"]\n    }\n  },\n  {\n    \"id\": 1666,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/privacy/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"patente.it\"]\n    }\n  },\n  {\n    \"id\": 1667,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/utag.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"tk-aerztefuehrer.de\"]\n    }\n  },\n  {\n    \"id\": 1668,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/rcs_cpmt/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"giroditalia.it\"]\n    }\n  },\n  {\n    \"id\": 1669,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"behindthename.com\"]\n    }\n  },\n  {\n    \"id\": 1670,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"it-cc.index.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"boellhoff.com\"]\n    }\n  },\n  {\n    \"id\": 1671,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiecontrol.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"rembutiken.se\"]\n    }\n  },\n  {\n    \"id\": 1672,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"deutsches-schulportal.de\"]\n    }\n  },\n  {\n    \"id\": 1673,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Custom_clubhinweis\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"ofdb.de\"]\n    }\n  },\n  {\n    \"id\": 1674,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"elpais.com\"]\n    }\n  },\n  {\n    \"id\": 1675,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.quantcast.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"pr0gramm.com\"]\n    }\n  },\n  {\n    \"id\": 1676,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"cmp.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"helthjem.no\"]\n    }\n  },\n  {\n    \"id\": 1677,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"webkamery.online\"]\n    }\n  },\n  {\n    \"id\": 1678,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/infrasdk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"nofluffjobs.com\"]\n    }\n  },\n  {\n    \"id\": 1679,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"diariodejerez.es\"]\n    }\n  },\n  {\n    \"id\": 1680,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"leonoticias.com\"]\n    }\n  },\n  {\n    \"id\": 1681,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookies_master.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"bayern.de\"]\n    }\n  },\n  {\n    \"id\": 1682,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"france24.com\"]\n    }\n  },\n  {\n    \"id\": 1683,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/CKCookieConsent.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"caseking.de\"]\n    }\n  },\n  {\n    \"id\": 1684,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gea-waldviertler.at\"]\n    }\n  },\n  {\n    \"id\": 1685,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookie-consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"gea-waldviertler.de\"]\n    }\n  },\n  {\n    \"id\": 1686,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ccm_\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"kro-ncrv.nl\"]\n    }\n  },\n  {\n    \"id\": 1687,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"retinatendencias.com\"]\n    }\n  },\n  {\n    \"id\": 1688,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/tac.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"avenuedelabrique.com\"]\n    }\n  },\n  {\n    \"id\": 1689,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"eldiadecordoba.es\"]\n    }\n  },\n  {\n    \"id\": 1690,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/ConsentManager/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"flvw.de\"]\n    }\n  },\n  {\n    \"id\": 1691,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"analytics-consent-manager\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"terveystalo.com\"]\n    }\n  },\n  {\n    \"id\": 1692,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"seznam.cz\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"firmy.cz\"]\n    }\n  },\n  {\n    \"id\": 1693,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-center.org\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"fameplay.tv\"]\n    }\n  },\n  {\n    \"id\": 1694,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"poool.fr/access.min.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"spektrum.de\"]\n    }\n  },\n  {\n    \"id\": 1695,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/wimc_gtm_consent/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"wingo.ch\"]\n    }\n  },\n  {\n    \"id\": 1696,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp_puk.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"commerzbank.de\"]\n    }\n  },\n  {\n    \"id\": 1697,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/scmp-int.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"mapy.cz\"]\n    }\n  },\n  {\n    \"id\": 1698,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"privacy-mgmt.com\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"immobilien.derstandard.at\"]\n    }\n  },\n  {\n    \"id\": 1699,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/js/privacy\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"atu.de\"]\n    }\n  },\n  {\n    \"id\": 1700,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/consent.\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"onlinestempel.ch\"]\n    }\n  },\n  {\n    \"id\": 1701,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cookiewall/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"saeco.de\"]\n    }\n  },\n  {\n    \"id\": 1702,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/Cookies.js\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"sage.co.uk\"]\n    }\n  },\n  {\n    \"id\": 1703,\n    \"priority\": 1,\n    \"action\": {\n      \"type\": \"block\"\n    },\n    \"condition\": {\n      \"urlFilter\": \"/cmp/\",\n      \"resourceTypes\": [\"script\", \"stylesheet\", \"xmlhttprequest\", \"image\"],\n      \"initiatorDomains\": [\"liebherr.com\"]\n    }\n  }\n]\n"
  },
  {
    "path": "pkg/engine/headless/browser/element.go",
    "content": "package browser\n\nimport (\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/types\"\n)\n\nconst (\n\t// buttonsCSSSelector is the css selector for all buttons\n\tbuttonsCSSSelector = \"button, input[type='button'], input[type='submit']\"\n\t// linksCSSSelector is the css selector for all anchor tags\n\tlinksCSSSelector = \"a\"\n)\n\n// isElementDisabled checks if a button element is disabled\nfunc isElementDisabled(element *types.HTMLElement) bool {\n\tif element.Attributes == nil {\n\t\treturn false\n\t}\n\n\t// Standard HTML disabled attribute\n\tif _, disabled := element.Attributes[\"disabled\"]; disabled {\n\t\treturn true\n\t}\n\n\t// Tailwind or framework class-based detection\n\tif classAttr, ok := element.Attributes[\"class\"]; ok {\n\t\tclassList := strings.Fields(classAttr)\n\t\tfor _, class := range classList {\n\t\t\tif class == \"cursor-not-allowed\" || class == \"pointer-events-none\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\n\t// Optionally support ARIA disabled\n\tif aria, ok := element.Attributes[\"aria-disabled\"]; ok && (aria == \"true\" || aria == \"1\") {\n\t\treturn true\n\t}\n\n\treturn false\n}\n\n// FindNavigation attempts to find more navigations on the page which could\n// be done to find more links and pages.\n//\n// This includes the following -\n//  1. Forms\n//  2. Buttons\n//  3. Links\n//  4. Elements with event listeners\n//\n// The navigations found are unique across the page. The caller\n// needs to ensure they are unique globally before doing further actions with details.\nfunc (b *BrowserPage) FindNavigations() ([]*types.Action, error) {\n\tunique := make(map[string]struct{})\n\n\tnavigations := make([]*types.Action, 0)\n\n\tforms, err := b.GetAllForms()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get forms\")\n\t}\n\tfor _, form := range forms {\n\t\tfor _, element := range form.Elements {\n\t\t\tif element.TagName != \"BUTTON\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// TODO: Check if this button is already in the unique map\n\t\t\t// and if so remove it\n\t\t\tunique[element.Hash()] = struct{}{}\n\t\t}\n\t\thash := form.Hash()\n\t\tif _, found := unique[hash]; found {\n\t\t\tcontinue\n\t\t}\n\t\tunique[hash] = struct{}{}\n\n\t\tnavigations = append(navigations, &types.Action{\n\t\t\tType: types.ActionTypeFillForm,\n\t\t\tForm: form,\n\t\t})\n\t}\n\n\tbuttons, err := b.GetAllElements(buttonsCSSSelector)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get buttons\")\n\t}\n\tfor _, button := range buttons {\n\t\tif isElementDisabled(button) {\n\t\t\tcontinue\n\t\t}\n\n\t\thash := button.Hash()\n\t\tbutton.MD5Hash = hash\n\n\t\tif _, found := unique[hash]; found {\n\t\t\tcontinue\n\t\t}\n\t\tunique[hash] = struct{}{}\n\t\tnavigations = append(navigations, &types.Action{\n\t\t\tType:    types.ActionTypeLeftClick,\n\t\t\tElement: button,\n\t\t})\n\t}\n\n\tscopeValidator := b.launcher.ScopeValidator()\n\tlinks, err := b.GetAllElements(linksCSSSelector)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get links\")\n\t}\n\tinfo, err := b.Info()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get page info\")\n\t}\n\tfor _, link := range links {\n\t\thref := link.Attributes[\"href\"]\n\t\tif href == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tresolvedHref, err := resolveURL(info.URL, href)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tu, err := url.Parse(resolvedHref)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif u.Scheme != \"http\" && u.Scheme != \"https\" {\n\t\t\tcontinue\n\t\t}\n\t\tif !scopeValidator(resolvedHref) {\n\t\t\tcontinue\n\t\t}\n\n\t\thash := link.Hash()\n\t\tlink.MD5Hash = hash\n\n\t\tif _, found := unique[hash]; found {\n\t\t\tcontinue\n\t\t}\n\t\tunique[hash] = struct{}{}\n\t\tnavigations = append(navigations, &types.Action{\n\t\t\tType:    types.ActionTypeLeftClick,\n\t\t\tElement: link,\n\t\t})\n\t}\n\n\teventListeners, err := b.GetEventListeners()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get event listeners\")\n\t}\n\tfor _, listener := range eventListeners {\n\t\tif _, found := relevantEventListeners[listener.Type]; !found {\n\t\t\tcontinue\n\t\t}\n\t\tif listener.Element == nil {\n\t\t\tcontinue\n\t\t}\n\t\thash := listener.Element.Hash()\n\t\tlistener.Element.MD5Hash = hash\n\t\tif _, found := unique[hash]; found {\n\t\t\tcontinue\n\t\t}\n\t\tunique[hash] = struct{}{}\n\t\tnavigations = append(navigations, types.ActionFromEventListener(listener))\n\t}\n\n\treturn navigations, nil\n}\n\nfunc (b *BrowserPage) GetAllElements(selector string) ([]*types.HTMLElement, error) {\n\tobjects, err := b.Eval(`() => window.getAllElements(` + strconv.Quote(selector) + `)`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\telements := make([]*types.HTMLElement, 0)\n\tif err := objects.Value.Unmarshal(&elements); err != nil {\n\t\treturn nil, err\n\t}\n\treturn elements, nil\n}\n\nfunc (b *BrowserPage) GetElementFromXpath(xpath string) (*types.HTMLElement, error) {\n\tobject, err := b.Eval(`() => window.getElementFromXPath(` + strconv.Quote(xpath) + `)`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\telement := &types.HTMLElement{}\n\tif err := object.Value.Unmarshal(element); err != nil {\n\t\treturn nil, err\n\t}\n\treturn element, nil\n}\n\nfunc (b *BrowserPage) GetAllForms() ([]*types.HTMLForm, error) {\n\tobjects, err := b.Eval(`() => window.getAllForms()`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\telements := make([]*types.HTMLForm, 0)\n\tif err := objects.Value.Unmarshal(&elements); err != nil {\n\t\treturn nil, err\n\t}\n\treturn elements, nil\n}\n\n// GetEventListeners returns all event listeners on the page\nfunc (b *BrowserPage) GetEventListeners() ([]*types.EventListener, error) {\n\tlisteners := make([]*types.EventListener, 0)\n\n\teventlisteners, err := b.Eval(`() => window.__eventListeners`)\n\tif err == nil {\n\t\t_ = eventlisteners.Value.Unmarshal(&listeners)\n\t}\n\n\t// Also get inline event listeners\n\tvar inlineEventListeners []struct {\n\t\tElement   *types.HTMLElement `json:\"element\"`\n\t\tListeners []struct {\n\t\t\tType     string `json:\"type\"`\n\t\t\tListener string `json:\"listener\"`\n\t\t} `json:\"listeners\"`\n\t}\n\tinlineListeners, err := b.Eval(`() => window.getAllElementsWithEventListeners()`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := inlineListeners.Value.Unmarshal(&inlineEventListeners); err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, inlineListener := range inlineEventListeners {\n\t\tfor _, listener := range inlineListener.Listeners {\n\t\t\tlistenerType := strings.TrimPrefix(listener.Type, \"on\")\n\t\t\tlisteners = append(listeners, &types.EventListener{\n\t\t\t\tType:     listenerType,\n\t\t\t\tListener: listener.Listener,\n\t\t\t\tElement:  inlineListener.Element,\n\t\t\t})\n\t\t}\n\t}\n\treturn listeners, nil\n}\n\n// NavigatedLink is a link navigated collected from one of the\n// navigation hooks.\ntype NavigatedLink struct {\n\tURL    string `json:\"url\"`\n\tSource string `json:\"source\"`\n}\n\n// GetNavigatedLinks returns all navigated links on the page\nfunc (b *BrowserPage) GetNavigatedLinks() ([]*NavigatedLink, error) {\n\tnavigatedLinks, err := b.Eval(`() => window.__navigatedLinks`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlisteners := make([]*NavigatedLink, 0)\n\tif err := navigatedLinks.Value.Unmarshal(&listeners); err != nil {\n\t\treturn nil, err\n\t}\n\treturn listeners, nil\n}\n\n// Define the map to hold event types\nvar relevantEventListeners = map[string]struct{}{\n\t// Focus and Blur events\n\t\"focusin\":  {},\n\t\"focus\":    {},\n\t\"blur\":     {},\n\t\"focusout\": {},\n\n\t// Click and Mouse events\n\t\"click\":       {},\n\t\"auxclick\":    {},\n\t\"mousedown\":   {},\n\t\"mouseup\":     {},\n\t\"dblclick\":    {},\n\t\"mouseover\":   {},\n\t\"mouseenter\":  {},\n\t\"mouseleave\":  {},\n\t\"mouseout\":    {},\n\t\"wheel\":       {},\n\t\"contextmenu\": {},\n\n\t// Key events\n\t\"keydown\":  {},\n\t\"keypress\": {},\n\t\"keyup\":    {},\n\n\t// Form events\n\t\"submit\": {},\n\t\"input\":  {},\n\t\"change\": {},\n}\n\n// resolveURL resolves a potentially relative URL against a base URL\nfunc resolveURL(baseURLStr, href string) (string, error) {\n\tbaseURL, err := url.Parse(baseURLStr)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tresolvedURL, err := baseURL.Parse(href)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resolvedURL.String(), nil\n}\n"
  },
  {
    "path": "pkg/engine/headless/browser/stealth/assets.go",
    "content": "// from stealth go-rod\npackage stealth\n\n// JS for stealth\nconst JS = `;(() => {\n\n(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:'utils => {\\n      if (!window.chrome) {\\n        // Use the exact property descriptor found in headful Chrome\\n        // fetch it via ` + \"`\" + `Object.getOwnPropertyDescriptor(window, \\'chrome\\')` + \"`\" + `\\n        Object.defineProperty(window, \\'chrome\\', {\\n          writable: true,\\n          enumerable: true,\\n          configurable: false, // note!\\n          value: {} // We\\'ll extend that later\\n        })\\n      }\\n\\n      // That means we\\'re running headful and don\\'t need to mock anything\\n      if (\\'app\\' in window.chrome) {\\n        return // Nothing to do here\\n      }\\n\\n      const makeError = {\\n        ErrorInInvocation: fn => {\\n          const err = new TypeError(` + \"`\" + `Error in invocation of app.${fn}()` + \"`\" + `)\\n          return utils.stripErrorWithAnchor(\\n            err,\\n            ` + \"`\" + `at ${fn} (eval at <anonymous>` + \"`\" + `\\n          )\\n        }\\n      }\\n\\n      // There\\'s a some static data in that property which doesn\\'t seem to change,\\n      // we should periodically check for updates: ` + \"`\" + `JSON.stringify(window.app, null, 2)` + \"`\" + `\\n      const STATIC_DATA = JSON.parse(\\n        ` + \"`\" + `\\n{\\n  \"isInstalled\": false,\\n  \"InstallState\": {\\n    \"DISABLED\": \"disabled\",\\n    \"INSTALLED\": \"installed\",\\n    \"NOT_INSTALLED\": \"not_installed\"\\n  },\\n  \"RunningState\": {\\n    \"CANNOT_RUN\": \"cannot_run\",\\n    \"READY_TO_RUN\": \"ready_to_run\",\\n    \"RUNNING\": \"running\"\\n  }\\n}\\n        ` + \"`\" + `.trim()\\n      )\\n\\n      window.chrome.app = {\\n        ...STATIC_DATA,\\n\\n        get isInstalled() {\\n          return false\\n        },\\n\\n        getDetails: function getDetails() {\\n          if (arguments.length) {\\n            throw makeError.ErrorInInvocation(` + \"`\" + `getDetails` + \"`\" + `)\\n          }\\n          return null\\n        },\\n        getIsInstalled: function getDetails() {\\n          if (arguments.length) {\\n            throw makeError.ErrorInInvocation(` + \"`\" + `getIsInstalled` + \"`\" + `)\\n          }\\n          return false\\n        },\\n        runningState: function getDetails() {\\n          if (arguments.length) {\\n            throw makeError.ErrorInInvocation(` + \"`\" + `runningState` + \"`\" + `)\\n          }\\n          return \\'cannot_run\\'\\n        }\\n      }\\n      utils.patchToStringNested(window.chrome.app)\\n    }',_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"utils => {\\n      if (!window.chrome) {\\n        // Use the exact property descriptor found in headful Chrome\\n        // fetch it via ` + \"`\" + `Object.getOwnPropertyDescriptor(window, 'chrome')` + \"`\" + `\\n        Object.defineProperty(window, 'chrome', {\\n          writable: true,\\n          enumerable: true,\\n          configurable: false, // note!\\n          value: {} // We'll extend that later\\n        })\\n      }\\n\\n      // That means we're running headful and don't need to mock anything\\n      if ('csi' in window.chrome) {\\n        return // Nothing to do here\\n      }\\n\\n      // Check that the Navigation Timing API v1 is available, we need that\\n      if (!window.performance || !window.performance.timing) {\\n        return\\n      }\\n\\n      const { timing } = window.performance\\n\\n      window.chrome.csi = function() {\\n        return {\\n          onloadT: timing.domContentLoadedEventEnd,\\n          startE: timing.navigationStart,\\n          pageT: Date.now() - timing.navigationStart,\\n          tran: 15 // Transition type or something\\n        }\\n      }\\n      utils.patchToString(window.chrome.csi)\\n    }\",_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, { opts }) => {\\n        if (!window.chrome) {\\n          // Use the exact property descriptor found in headful Chrome\\n          // fetch it via ` + \"`\" + `Object.getOwnPropertyDescriptor(window, 'chrome')` + \"`\" + `\\n          Object.defineProperty(window, 'chrome', {\\n            writable: true,\\n            enumerable: true,\\n            configurable: false, // note!\\n            value: {} // We'll extend that later\\n          })\\n        }\\n\\n        // That means we're running headful and don't need to mock anything\\n        if ('loadTimes' in window.chrome) {\\n          return // Nothing to do here\\n        }\\n\\n        // Check that the Navigation Timing API v1 + v2 is available, we need that\\n        if (\\n          !window.performance ||\\n          !window.performance.timing ||\\n          !window.PerformancePaintTiming\\n        ) {\\n          return\\n        }\\n\\n        const { performance } = window\\n\\n        // Some stuff is not available on about:blank as it requires a navigation to occur,\\n        // let's harden the code to not fail then:\\n        const ntEntryFallback = {\\n          nextHopProtocol: 'h2',\\n          type: 'other'\\n        }\\n\\n        // The API exposes some funky info regarding the connection\\n        const protocolInfo = {\\n          get connectionInfo() {\\n            const ntEntry =\\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\\n            return ntEntry.nextHopProtocol\\n          },\\n          get npnNegotiatedProtocol() {\\n            // NPN is deprecated in favor of ALPN, but this implementation returns the\\n            // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\\n            const ntEntry =\\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\\n              ? ntEntry.nextHopProtocol\\n              : 'unknown'\\n          },\\n          get navigationType() {\\n            const ntEntry =\\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\\n            return ntEntry.type\\n          },\\n          get wasAlternateProtocolAvailable() {\\n            // The Alternate-Protocol header is deprecated in favor of Alt-Svc\\n            // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this\\n            // should always return false.\\n            return false\\n          },\\n          get wasFetchedViaSpdy() {\\n            // SPDY is deprecated in favor of HTTP/2, but this implementation returns\\n            // true for HTTP/2 or HTTP2+QUIC/39 as well.\\n            const ntEntry =\\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\\n          },\\n          get wasNpnNegotiated() {\\n            // NPN is deprecated in favor of ALPN, but this implementation returns true\\n            // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN.\\n            const ntEntry =\\n              performance.getEntriesByType('navigation')[0] || ntEntryFallback\\n            return ['h2', 'hq'].includes(ntEntry.nextHopProtocol)\\n          }\\n        }\\n\\n        const { timing } = window.performance\\n\\n        // Truncate number to specific number of decimals, most of the ` + \"`\" + `loadTimes` + \"`\" + ` stuff has 3\\n        function toFixed(num, fixed) {\\n          var re = new RegExp('^-?\\\\\\\\d+(?:.\\\\\\\\d{0,' + (fixed || -1) + '})?')\\n          return num.toString().match(re)[0]\\n        }\\n\\n        const timingInfo = {\\n          get firstPaintAfterLoadTime() {\\n            // This was never actually implemented and always returns 0.\\n            return 0\\n          },\\n          get requestTime() {\\n            return timing.navigationStart / 1000\\n          },\\n          get startLoadTime() {\\n            return timing.navigationStart / 1000\\n          },\\n          get commitLoadTime() {\\n            return timing.responseStart / 1000\\n          },\\n          get finishDocumentLoadTime() {\\n            return timing.domContentLoadedEventEnd / 1000\\n          },\\n          get finishLoadTime() {\\n            return timing.loadEventEnd / 1000\\n          },\\n          get firstPaintTime() {\\n            const fpEntry = performance.getEntriesByType('paint')[0] || {\\n              startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occurred (` + \"`\" + `about:blank` + \"`\" + `)\\n            }\\n            return toFixed(\\n              (fpEntry.startTime + performance.timeOrigin) / 1000,\\n              3\\n            )\\n          }\\n        }\\n\\n        window.chrome.loadTimes = function() {\\n          return {\\n            ...protocolInfo,\\n            ...timingInfo\\n          }\\n        }\\n        utils.patchToString(window.chrome.loadTimes)\\n      }\",_args:[{opts:{}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, { opts, STATIC_DATA }) => {\\n        if (!window.chrome) {\\n          // Use the exact property descriptor found in headful Chrome\\n          // fetch it via ` + \"`\" + `Object.getOwnPropertyDescriptor(window, 'chrome')` + \"`\" + `\\n          Object.defineProperty(window, 'chrome', {\\n            writable: true,\\n            enumerable: true,\\n            configurable: false, // note!\\n            value: {} // We'll extend that later\\n          })\\n        }\\n\\n        // That means we're running headful and don't need to mock anything\\n        const existsAlready = 'runtime' in window.chrome\\n        // ` + \"`\" + `chrome.runtime` + \"`\" + ` is only exposed on secure origins\\n        const isNotSecure = !window.location.protocol.startsWith('https')\\n        if (existsAlready || (isNotSecure && !opts.runOnInsecureOrigins)) {\\n          return // Nothing to do here\\n        }\\n\\n        window.chrome.runtime = {\\n          // There's a bunch of static data in that property which doesn't seem to change,\\n          // we should periodically check for updates: ` + \"`\" + `JSON.stringify(window.chrome.runtime, null, 2)` + \"`\" + `\\n          ...STATIC_DATA,\\n          // ` + \"`\" + `chrome.runtime.id` + \"`\" + ` is extension related and returns undefined in Chrome\\n          get id() {\\n            return undefined\\n          },\\n          // These two require more sophisticated mocks\\n          connect: null,\\n          sendMessage: null\\n        }\\n\\n        const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({\\n          NoMatchingSignature: new TypeError(\\n            preamble + ` + \"`\" + `No matching signature.` + \"`\" + `\\n          ),\\n          MustSpecifyExtensionID: new TypeError(\\n            preamble +\\n              ` + \"`\" + `${method} called from a webpage must specify an Extension ID (string) for its first argument.` + \"`\" + `\\n          ),\\n          InvalidExtensionID: new TypeError(\\n            preamble + ` + \"`\" + `Invalid extension id: '${extensionId}'` + \"`\" + `\\n          )\\n        })\\n\\n        // Valid Extension IDs are 32 characters in length and use the letter ` + \"`\" + `a` + \"`\" + ` to ` + \"`\" + `p` + \"`\" + `:\\n        // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90\\n        const isValidExtensionID = str =>\\n          str.length === 32 && str.toLowerCase().match(/^[a-p]+$/)\\n\\n        /** Mock ` + \"`\" + `chrome.runtime.sendMessage` + \"`\" + ` */\\n        const sendMessageHandler = {\\n          apply: function(target, ctx, args) {\\n            const [extensionId, options, responseCallback] = args || []\\n\\n            // Define custom errors\\n            const errorPreamble = ` + \"`\" + `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): ` + \"`\" + `\\n            const Errors = makeCustomRuntimeErrors(\\n              errorPreamble,\\n              ` + \"`\" + `chrome.runtime.sendMessage()` + \"`\" + `,\\n              extensionId\\n            )\\n\\n            // Check if the call signature looks ok\\n            const noArguments = args.length === 0\\n            const tooManyArguments = args.length > 4\\n            const incorrectOptions = options && typeof options !== 'object'\\n            const incorrectResponseCallback =\\n              responseCallback && typeof responseCallback !== 'function'\\n            if (\\n              noArguments ||\\n              tooManyArguments ||\\n              incorrectOptions ||\\n              incorrectResponseCallback\\n            ) {\\n              throw Errors.NoMatchingSignature\\n            }\\n\\n            // At least 2 arguments are required before we even validate the extension ID\\n            if (args.length < 2) {\\n              throw Errors.MustSpecifyExtensionID\\n            }\\n\\n            // Now let's make sure we got a string as extension ID\\n            if (typeof extensionId !== 'string') {\\n              throw Errors.NoMatchingSignature\\n            }\\n\\n            if (!isValidExtensionID(extensionId)) {\\n              throw Errors.InvalidExtensionID\\n            }\\n\\n            return undefined // Normal behavior\\n          }\\n        }\\n        utils.mockWithProxy(\\n          window.chrome.runtime,\\n          'sendMessage',\\n          function sendMessage() {},\\n          sendMessageHandler\\n        )\\n\\n        /**\\n         * Mock ` + \"`\" + `chrome.runtime.connect` + \"`\" + `\\n         *\\n         * @see https://developer.chrome.com/apps/runtime#method-connect\\n         */\\n        const connectHandler = {\\n          apply: function(target, ctx, args) {\\n            const [extensionId, connectInfo] = args || []\\n\\n            // Define custom errors\\n            const errorPreamble = ` + \"`\" + `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): ` + \"`\" + `\\n            const Errors = makeCustomRuntimeErrors(\\n              errorPreamble,\\n              ` + \"`\" + `chrome.runtime.connect()` + \"`\" + `,\\n              extensionId\\n            )\\n\\n            // Behavior differs a bit from sendMessage:\\n            const noArguments = args.length === 0\\n            const emptyStringArgument = args.length === 1 && extensionId === ''\\n            if (noArguments || emptyStringArgument) {\\n              throw Errors.MustSpecifyExtensionID\\n            }\\n\\n            const tooManyArguments = args.length > 2\\n            const incorrectConnectInfoType =\\n              connectInfo && typeof connectInfo !== 'object'\\n\\n            if (tooManyArguments || incorrectConnectInfoType) {\\n              throw Errors.NoMatchingSignature\\n            }\\n\\n            const extensionIdIsString = typeof extensionId === 'string'\\n            if (extensionIdIsString && extensionId === '') {\\n              throw Errors.MustSpecifyExtensionID\\n            }\\n            if (extensionIdIsString && !isValidExtensionID(extensionId)) {\\n              throw Errors.InvalidExtensionID\\n            }\\n\\n            // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validate\\n            const validateConnectInfo = ci => {\\n              // More than a first param connectInfo as been provided\\n              if (args.length > 1) {\\n                throw Errors.NoMatchingSignature\\n              }\\n              // An empty connectInfo has been provided\\n              if (Object.keys(ci).length === 0) {\\n                throw Errors.MustSpecifyExtensionID\\n              }\\n              // Loop over all connectInfo props an check them\\n              Object.entries(ci).forEach(([k, v]) => {\\n                const isExpected = ['name', 'includeTlsChannelId'].includes(k)\\n                if (!isExpected) {\\n                  throw new TypeError(\\n                    errorPreamble + ` + \"`\" + `Unexpected property: '${k}'.` + \"`\" + `\\n                  )\\n                }\\n                const MismatchError = (propName, expected, found) =>\\n                  TypeError(\\n                    errorPreamble +\\n                      ` + \"`\" + `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.` + \"`\" + `\\n                  )\\n                if (k === 'name' && typeof v !== 'string') {\\n                  throw MismatchError(k, 'string', typeof v)\\n                }\\n                if (k === 'includeTlsChannelId' && typeof v !== 'boolean') {\\n                  throw MismatchError(k, 'boolean', typeof v)\\n                }\\n              })\\n            }\\n            if (typeof extensionId === 'object') {\\n              validateConnectInfo(extensionId)\\n              throw Errors.MustSpecifyExtensionID\\n            }\\n\\n            // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as well\\n            return utils.patchToStringNested(makeConnectResponse())\\n          }\\n        }\\n        utils.mockWithProxy(\\n          window.chrome.runtime,\\n          'connect',\\n          function connect() {},\\n          connectHandler\\n        )\\n\\n        function makeConnectResponse() {\\n          const onSomething = () => ({\\n            addListener: function addListener() {},\\n            dispatch: function dispatch() {},\\n            hasListener: function hasListener() {},\\n            hasListeners: function hasListeners() {\\n              return false\\n            },\\n            removeListener: function removeListener() {}\\n          })\\n\\n          const response = {\\n            name: '',\\n            sender: undefined,\\n            disconnect: function disconnect() {},\\n            onDisconnect: onSomething(),\\n            onMessage: onSomething(),\\n            postMessage: function postMessage() {\\n              if (!arguments.length) {\\n                throw new TypeError(` + \"`\" + `Insufficient number of arguments.` + \"`\" + `)\\n              }\\n              throw new Error(` + \"`\" + `Attempting to use a disconnected port object` + \"`\" + `)\\n            }\\n          }\\n          return response\\n        }\\n      }\",_args:[{opts:{runOnInsecureOrigins:!1},STATIC_DATA:{OnInstalledReason:{CHROME_UPDATE:\"chrome_update\",INSTALL:\"install\",SHARED_MODULE_UPDATE:\"shared_module_update\",UPDATE:\"update\"},OnRestartRequiredReason:{APP_UPDATE:\"app_update\",OS_UPDATE:\"os_update\",PERIODIC:\"periodic\"},PlatformArch:{ARM:\"arm\",ARM64:\"arm64\",MIPS:\"mips\",MIPS64:\"mips64\",X86_32:\"x86-32\",X86_64:\"x86-64\"},PlatformNaclArch:{ARM:\"arm\",MIPS:\"mips\",MIPS64:\"mips64\",X86_32:\"x86-32\",X86_64:\"x86-64\"},PlatformOs:{ANDROID:\"android\",CROS:\"cros\",LINUX:\"linux\",MAC:\"mac\",OPENBSD:\"openbsd\",WIN:\"win\"},RequestUpdateCheckStatus:{NO_UPDATE:\"no_update\",THROTTLED:\"throttled\",UPDATE_AVAILABLE:\"update_available\"}}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"utils => {\\n      /**\\n       * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing.\\n       *\\n       * @example\\n       * video/webm; codecs=\\\"vp8, vorbis\\\"\\n       * video/mp4; codecs=\\\"avc1.42E01E\\\"\\n       * audio/x-m4a;\\n       * audio/ogg; codecs=\\\"vorbis\\\"\\n       * @param {String} arg\\n       */\\n      const parseInput = arg => {\\n        const [mime, codecStr] = arg.trim().split(';')\\n        let codecs = []\\n        if (codecStr && codecStr.includes('codecs=\\\"')) {\\n          codecs = codecStr\\n            .trim()\\n            .replace(` + \"`\" + `codecs=\\\"` + \"`\" + `, '')\\n            .replace(` + \"`\" + `\\\"` + \"`\" + `, '')\\n            .trim()\\n            .split(',')\\n            .filter(x => !!x)\\n            .map(x => x.trim())\\n        }\\n        return {\\n          mime,\\n          codecStr,\\n          codecs\\n        }\\n      }\\n\\n      const canPlayType = {\\n        // Intercept certain requests\\n        apply: function(target, ctx, args) {\\n          if (!args || !args.length) {\\n            return target.apply(ctx, args)\\n          }\\n          const { mime, codecs } = parseInput(args[0])\\n          // This specific mp4 codec is missing in Chromium\\n          if (mime === 'video/mp4') {\\n            if (codecs.includes('avc1.42E01E')) {\\n              return 'probably'\\n            }\\n          }\\n          // This mimetype is only supported if no codecs are specified\\n          if (mime === 'audio/x-m4a' && !codecs.length) {\\n            return 'maybe'\\n          }\\n\\n          // This mimetype is only supported if no codecs are specified\\n          if (mime === 'audio/aac' && !codecs.length) {\\n            return 'probably'\\n          }\\n          // Everything else as usual\\n          return target.apply(ctx, args)\\n        }\\n      }\\n\\n      /* global HTMLMediaElement */\\n      utils.replaceWithProxy(\\n        HTMLMediaElement.prototype,\\n        'canPlayType',\\n        canPlayType\\n      )\\n    }\",_args:[]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, { opts }) => {\\n        utils.replaceGetterWithProxy(\\n          Object.getPrototypeOf(navigator),\\n          'hardwareConcurrency',\\n          utils.makeHandler().getterValue(opts.hardwareConcurrency)\\n        )\\n      }\",_args:[{opts:{hardwareConcurrency:4}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, { opts }) => {\\n        const languages = opts.languages.length\\n          ? opts.languages\\n          : ['en-US', 'en']\\n        utils.replaceGetterWithProxy(\\n          Object.getPrototypeOf(navigator),\\n          'languages',\\n          utils.makeHandler().getterValue(Object.freeze([...languages]))\\n        )\\n      }\",_args:[{opts:{languages:[]}}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, opts) => {\\n      const isSecure = document.location.protocol.startsWith('https')\\n\\n      // In headful on secure origins the permission should be \\\"default\\\", not \\\"denied\\\"\\n      if (isSecure) {\\n        utils.replaceGetterWithProxy(Notification, 'permission', {\\n          apply() {\\n            return 'default'\\n          }\\n        })\\n      }\\n\\n      // Another weird behavior:\\n      // On insecure origins in headful the state is \\\"denied\\\",\\n      // whereas in headless it's \\\"prompt\\\"\\n      if (!isSecure) {\\n        const handler = {\\n          apply(target, ctx, args) {\\n            const param = (args || [])[0]\\n\\n            const isNotifications =\\n              param && param.name && param.name === 'notifications'\\n            if (!isNotifications) {\\n              return utils.cache.Reflect.apply(...arguments)\\n            }\\n\\n            return Promise.resolve(\\n              Object.setPrototypeOf(\\n                {\\n                  state: 'denied',\\n                  onchange: null\\n                },\\n                PermissionStatus.prototype\\n              )\\n            )\\n          }\\n        }\\n        // Note: Don't use ` + \"`\" + `Object.getPrototypeOf` + \"`\" + ` here\\n        utils.replaceWithProxy(Permissions.prototype, 'query', handler)\\n      }\\n    }\",_args:[{}]}),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, { fns, data }) => {\\n        fns = utils.materializeFns(fns)\\n\\n        // That means we're running headful\\n        const hasPlugins = 'plugins' in navigator && navigator.plugins.length\\n        if (hasPlugins) {\\n          return // nothing to do here\\n        }\\n\\n        const mimeTypes = fns.generateMimeTypeArray(utils, fns)(data.mimeTypes)\\n        const plugins = fns.generatePluginArray(utils, fns)(data.plugins)\\n\\n        // Plugin and MimeType cross-reference each other, let's do that now\\n        // Note: We're looping through ` + \"`\" + `data.plugins` + \"`\" + ` here, not the generated ` + \"`\" + `plugins` + \"`\" + `\\n        for (const pluginData of data.plugins) {\\n          pluginData.__mimeTypes.forEach((type, index) => {\\n            plugins[pluginData.name][index] = mimeTypes[type]\\n\\n            Object.defineProperty(plugins[pluginData.name], type, {\\n              value: mimeTypes[type],\\n              writable: false,\\n              enumerable: false, // Not enumerable\\n              configurable: true\\n            })\\n            Object.defineProperty(mimeTypes[type], 'enabledPlugin', {\\n              value:\\n                type === 'application/x-pnacl'\\n                  ? mimeTypes['application/x-nacl'].enabledPlugin // these reference the same plugin, so we need to re-use the Proxy in order to avoid leaks\\n                  : new Proxy(plugins[pluginData.name], {}), // Prevent circular references\\n              writable: false,\\n              enumerable: false, // Important: ` + \"`\" + `JSON.stringify(navigator.plugins)` + \"`\" + `\\n              configurable: true\\n            })\\n          })\\n        }\\n\\n        const patchNavigator = (name, value) =>\\n          utils.replaceProperty(Object.getPrototypeOf(navigator), name, {\\n            get() {\\n              return value\\n            }\\n          })\\n\\n        patchNavigator('mimeTypes', mimeTypes)\\n        patchNavigator('plugins', plugins)\\n\\n        // All done\\n      }\",_args:[{fns:{generateMimeTypeArray:\"(utils, fns) => mimeTypesData => {\\n  return fns.generateMagicArray(utils, fns)(\\n    mimeTypesData,\\n    MimeTypeArray.prototype,\\n    MimeType.prototype,\\n    'type'\\n  )\\n}\",generatePluginArray:\"(utils, fns) => pluginsData => {\\n  return fns.generateMagicArray(utils, fns)(\\n    pluginsData,\\n    PluginArray.prototype,\\n    Plugin.prototype,\\n    'name'\\n  )\\n}\",generateMagicArray:\"(utils, fns) =>\\n  function(\\n    dataArray = [],\\n    proto = MimeTypeArray.prototype,\\n    itemProto = MimeType.prototype,\\n    itemMainProp = 'type'\\n  ) {\\n    // Quick helper to set props with the same descriptors vanilla is using\\n    const defineProp = (obj, prop, value) =>\\n      Object.defineProperty(obj, prop, {\\n        value,\\n        writable: false,\\n        enumerable: false, // Important for mimeTypes & plugins: ` + \"`\" + `JSON.stringify(navigator.mimeTypes)` + \"`\" + `\\n        configurable: true\\n      })\\n\\n    // Loop over our fake data and construct items\\n    const makeItem = data => {\\n      const item = {}\\n      for (const prop of Object.keys(data)) {\\n        if (prop.startsWith('__')) {\\n          continue\\n        }\\n        defineProp(item, prop, data[prop])\\n      }\\n      return patchItem(item, data)\\n    }\\n\\n    const patchItem = (item, data) => {\\n      let descriptor = Object.getOwnPropertyDescriptors(item)\\n\\n      // Special case: Plugins have a magic length property which is not enumerable\\n      // e.g. ` + \"`\" + `navigator.plugins[i].length` + \"`\" + ` should always be the length of the assigned mimeTypes\\n      if (itemProto === Plugin.prototype) {\\n        descriptor = {\\n          ...descriptor,\\n          length: {\\n            value: data.__mimeTypes.length,\\n            writable: false,\\n            enumerable: false,\\n            configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip ` + \"`\" + `length` + \"`\" + `\\n          }\\n        }\\n      }\\n\\n      // We need to spoof a specific ` + \"`\" + `MimeType` + \"`\" + ` or ` + \"`\" + `Plugin` + \"`\" + ` object\\n      const obj = Object.create(itemProto, descriptor)\\n\\n      // Virtually all property keys are not enumerable in vanilla\\n      const blacklist = [...Object.keys(data), 'length', 'enabledPlugin']\\n      return new Proxy(obj, {\\n        ownKeys(target) {\\n          return Reflect.ownKeys(target).filter(k => !blacklist.includes(k))\\n        },\\n        getOwnPropertyDescriptor(target, prop) {\\n          if (blacklist.includes(prop)) {\\n            return undefined\\n          }\\n          return Reflect.getOwnPropertyDescriptor(target, prop)\\n        }\\n      })\\n    }\\n\\n    const magicArray = []\\n\\n    // Loop through our fake data and use that to create convincing entities\\n    dataArray.forEach(data => {\\n      magicArray.push(makeItem(data))\\n    })\\n\\n    // Add direct property access  based on types (e.g. ` + \"`\" + `obj['application/pdf']` + \"`\" + `) afterwards\\n    magicArray.forEach(entry => {\\n      defineProp(magicArray, entry[itemMainProp], entry)\\n    })\\n\\n    // This is the best way to fake the type to make sure this is false: ` + \"`\" + `Array.isArray(navigator.mimeTypes)` + \"`\" + `\\n    const magicArrayObj = Object.create(proto, {\\n      ...Object.getOwnPropertyDescriptors(magicArray),\\n\\n      // There's one ugly quirk we unfortunately need to take care of:\\n      // The ` + \"`\" + `MimeTypeArray` + \"`\" + ` prototype has an enumerable ` + \"`\" + `length` + \"`\" + ` property,\\n      // but headful Chrome will still skip it when running ` + \"`\" + `Object.getOwnPropertyNames(navigator.mimeTypes)` + \"`\" + `.\\n      // To strip it we need to make it first ` + \"`\" + `configurable` + \"`\" + ` and can then overlay a Proxy with an ` + \"`\" + `ownKeys` + \"`\" + ` trap.\\n      length: {\\n        value: magicArray.length,\\n        writable: false,\\n        enumerable: false,\\n        configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip ` + \"`\" + `length` + \"`\" + `\\n      }\\n    })\\n\\n    // Generate our functional function mocks :-)\\n    const functionMocks = fns.generateFunctionMocks(utils)(\\n      proto,\\n      itemMainProp,\\n      magicArray\\n    )\\n\\n    // We need to overlay our custom object with a JS Proxy\\n    const magicArrayObjProxy = new Proxy(magicArrayObj, {\\n      get(target, key = '') {\\n        // Redirect function calls to our custom proxied versions mocking the vanilla behavior\\n        if (key === 'item') {\\n          return functionMocks.item\\n        }\\n        if (key === 'namedItem') {\\n          return functionMocks.namedItem\\n        }\\n        if (proto === PluginArray.prototype && key === 'refresh') {\\n          return functionMocks.refresh\\n        }\\n        // Everything else can pass through as normal\\n        return utils.cache.Reflect.get(...arguments)\\n      },\\n      ownKeys(target) {\\n        // There are a couple of quirks where the original property demonstrates \\\"magical\\\" behavior that makes no sense\\n        // This can be witnessed when calling ` + \"`\" + `Object.getOwnPropertyNames(navigator.mimeTypes)` + \"`\" + ` and the absence of ` + \"`\" + `length` + \"`\" + `\\n        // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly\\n        // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing\\n        // Specific issues when not patching this: ` + \"`\" + `length` + \"`\" + ` property is available, direct ` + \"`\" + `types` + \"`\" + ` props (e.g. ` + \"`\" + `obj['application/pdf']` + \"`\" + `) are missing\\n        const keys = []\\n        const typeProps = magicArray.map(mt => mt[itemMainProp])\\n        typeProps.forEach((_, i) => keys.push(` + \"`\" + `${i}` + \"`\" + `))\\n        typeProps.forEach(propName => keys.push(propName))\\n        return keys\\n      },\\n      getOwnPropertyDescriptor(target, prop) {\\n        if (prop === 'length') {\\n          return undefined\\n        }\\n        return Reflect.getOwnPropertyDescriptor(target, prop)\\n      }\\n    })\\n\\n    return magicArrayObjProxy\\n  }\",generateFunctionMocks:\"utils => (\\n  proto,\\n  itemMainProp,\\n  dataArray\\n) => ({\\n  /** Returns the MimeType object with the specified index. */\\n  item: utils.createProxy(proto.item, {\\n    apply(target, ctx, args) {\\n      if (!args.length) {\\n        throw new TypeError(\\n          ` + \"`\" + `Failed to execute 'item' on '${\\n            proto[Symbol.toStringTag]\\n          }': 1 argument required, but only 0 present.` + \"`\" + `\\n        )\\n      }\\n      // Special behavior alert:\\n      // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup\\n      // - If anything else than an integer (including as string) is provided it will return the first entry\\n      const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integer\\n      // Note: Vanilla never returns ` + \"`\" + `undefined` + \"`\" + `\\n      return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null\\n    }\\n  }),\\n  /** Returns the MimeType object with the specified name. */\\n  namedItem: utils.createProxy(proto.namedItem, {\\n    apply(target, ctx, args) {\\n      if (!args.length) {\\n        throw new TypeError(\\n          ` + \"`\" + `Failed to execute 'namedItem' on '${\\n            proto[Symbol.toStringTag]\\n          }': 1 argument required, but only 0 present.` + \"`\" + `\\n        )\\n      }\\n      return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not ` + \"`\" + `undefined` + \"`\" + `!\\n    }\\n  }),\\n  /** Does nothing and shall return nothing */\\n  refresh: proto.refresh\\n    ? utils.createProxy(proto.refresh, {\\n        apply(target, ctx, args) {\\n          return undefined\\n        }\\n      })\\n    : undefined\\n})\"},data:{mimeTypes:[{type:\"application/pdf\",suffixes:\"pdf\",description:\"\",__pluginName:\"Chrome PDF Viewer\"},{type:\"application/x-google-chrome-pdf\",suffixes:\"pdf\",description:\"Portable Document Format\",__pluginName:\"Chrome PDF Plugin\"},{type:\"application/x-nacl\",suffixes:\"\",description:\"Native Client Executable\",__pluginName:\"Native Client\"},{type:\"application/x-pnacl\",suffixes:\"\",description:\"Portable Native Client Executable\",__pluginName:\"Native Client\"}],plugins:[{name:\"Chrome PDF Plugin\",filename:\"internal-pdf-viewer\",description:\"Portable Document Format\",__mimeTypes:[\"application/x-google-chrome-pdf\"]},{name:\"Chrome PDF Viewer\",filename:\"mhjfbmdgcfjbbpaeojofohoefgiehjai\",description:\"\",__mimeTypes:[\"application/pdf\"]},{name:\"Native Client\",filename:\"internal-nacl-plugin\",description:\"\",__mimeTypes:[\"application/x-nacl\",\"application/x-pnacl\"]}]}}]}),!1===navigator.webdriver||void 0===navigator.webdriver||delete Object.getPrototypeOf(navigator).webdriver,(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, opts) => {\\n      const getParameterProxyHandler = {\\n        apply: function(target, ctx, args) {\\n          const param = (args || [])[0]\\n          const result = utils.cache.Reflect.apply(target, ctx, args)\\n          // UNMASKED_VENDOR_WEBGL\\n          if (param === 37445) {\\n            return opts.vendor || 'Intel Inc.' // default in headless: Google Inc.\\n          }\\n          // UNMASKED_RENDERER_WEBGL\\n          if (param === 37446) {\\n            return opts.renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader\\n          }\\n          return result\\n        }\\n      }\\n\\n      // There's more than one WebGL rendering context\\n      // https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility\\n      // To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter)\\n      const addProxy = (obj, propName) => {\\n        utils.replaceWithProxy(obj, propName, getParameterProxyHandler)\\n      }\\n      // For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing:\\n      addProxy(WebGLRenderingContext.prototype, 'getParameter')\\n      addProxy(WebGL2RenderingContext.prototype, 'getParameter')\\n    }\",_args:[{}]}),(()=>{try{if(window.outerWidth&&window.outerHeight)return;const n=85;window.outerWidth=window.innerWidth,window.outerHeight=window.innerHeight+n}catch(n){}})(),(({_utilsFns:_utilsFns,_mainFunction:_mainFunction,_args:_args})=>{const utils=Object.fromEntries(Object.entries(_utilsFns).map((([key,value])=>[key,eval(value)])));utils.init(),eval(_mainFunction)(utils,..._args)})({_utilsFns:{init:\"() => {\\n  utils.preloadCache()\\n}\",stripProxyFromErrors:\"(handler = {}) => {\\n  const newHandler = {\\n    setPrototypeOf: function (target, proto) {\\n      if (proto === null)\\n        throw new TypeError('Cannot convert object to primitive value')\\n      if (Object.getPrototypeOf(target) === Object.getPrototypeOf(proto)) {\\n        throw new TypeError('Cyclic __proto__ value')\\n      }\\n      return Reflect.setPrototypeOf(target, proto)\\n    }\\n  }\\n  // We wrap each trap in the handler in a try/catch and modify the error stack if they throw\\n  const traps = Object.getOwnPropertyNames(handler)\\n  traps.forEach(trap => {\\n    newHandler[trap] = function () {\\n      try {\\n        // Forward the call to the defined proxy handler\\n        return handler[trap].apply(this, arguments || [])\\n      } catch (err) {\\n        // Stack traces differ per browser, we only support chromium based ones currently\\n        if (!err || !err.stack || !err.stack.includes(` + \"`\" + `at ` + \"`\" + `)) {\\n          throw err\\n        }\\n\\n        // When something throws within one of our traps the Proxy will show up in error stacks\\n        // An earlier implementation of this code would simply strip lines with a blacklist,\\n        // but it makes sense to be more surgical here and only remove lines related to our Proxy.\\n        // We try to use a known \\\"anchor\\\" line for that and strip it with everything above it.\\n        // If the anchor line cannot be found for some reason we fall back to our blacklist approach.\\n\\n        const stripWithBlacklist = (stack, stripFirstLine = true) => {\\n          const blacklist = [\\n            ` + \"`\" + `at Reflect.${trap} ` + \"`\" + `, // e.g. Reflect.get or Reflect.apply\\n            ` + \"`\" + `at Object.${trap} ` + \"`\" + `, // e.g. Object.get or Object.apply\\n            ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // caused by this very wrapper :-)\\n          ]\\n          return (\\n            err.stack\\n              .split('\\\\n')\\n              // Always remove the first (file) line in the stack (guaranteed to be our proxy)\\n              .filter((line, index) => !(index === 1 && stripFirstLine))\\n              // Check if the line starts with one of our blacklisted strings\\n              .filter(line => !blacklist.some(bl => line.trim().startsWith(bl)))\\n              .join('\\\\n')\\n          )\\n        }\\n\\n        const stripWithAnchor = (stack, anchor) => {\\n          const stackArr = stack.split('\\\\n')\\n          anchor = anchor || ` + \"`\" + `at Object.newHandler.<computed> [as ${trap}] ` + \"`\" + ` // Known first Proxy line in chromium\\n          const anchorIndex = stackArr.findIndex(line =>\\n            line.trim().startsWith(anchor)\\n          )\\n          if (anchorIndex === -1) {\\n            return false // 404, anchor not found\\n          }\\n          // Strip everything from the top until we reach the anchor line\\n          // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n          stackArr.splice(1, anchorIndex)\\n          return stackArr.join('\\\\n')\\n        }\\n\\n        // Special cases due to our nested toString proxies\\n        err.stack = err.stack.replace(\\n          'at Object.toString (',\\n          'at Function.toString ('\\n        )\\n        if ((err.stack || '').includes('at Function.toString (')) {\\n          err.stack = stripWithBlacklist(err.stack, false)\\n          throw err\\n        }\\n\\n        // Try using the anchor method, fallback to blacklist if necessary\\n        err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack)\\n\\n        throw err // Re-throw our now sanitized error\\n      }\\n    }\\n  })\\n  return newHandler\\n}\",stripErrorWithAnchor:\"(err, anchor) => {\\n  const stackArr = err.stack.split('\\\\n')\\n  const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor))\\n  if (anchorIndex === -1) {\\n    return err // 404, anchor not found\\n  }\\n  // Strip everything from the top until we reach the anchor line (remove anchor line as well)\\n  // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. ` + \"`\" + `TypeError` + \"`\" + `)\\n  stackArr.splice(1, anchorIndex)\\n  err.stack = stackArr.join('\\\\n')\\n  return err\\n}\",replaceProperty:\"(obj, propName, descriptorOverrides = {}) => {\\n  return Object.defineProperty(obj, propName, {\\n    // Copy over the existing descriptors (writable, enumerable, configurable, etc)\\n    ...(Object.getOwnPropertyDescriptor(obj, propName) || {}),\\n    // Add our overrides (e.g. value, get())\\n    ...descriptorOverrides\\n  })\\n}\",preloadCache:\"() => {\\n  if (utils.cache) {\\n    return\\n  }\\n  utils.cache = {\\n    // Used in our proxies\\n    Reflect: {\\n      get: Reflect.get.bind(Reflect),\\n      apply: Reflect.apply.bind(Reflect)\\n    },\\n    // Used in ` + \"`\" + `makeNativeString` + \"`\" + `\\n    nativeToStringStr: Function.toString + '' // => ` + \"`\" + `function toString() { [native code] }` + \"`\" + `\\n  }\\n}\",makeNativeString:\"(name = '') => {\\n  return utils.cache.nativeToStringStr.replace('toString', name || '')\\n}\",patchToString:\"(obj, str = '') => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === obj) {\\n        // We either return the optional string verbatim or derive the most desired result automatically\\n        return str || utils.makeNativeString(obj.name)\\n      }\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",patchToStringNested:\"(obj = {}) => {\\n  return utils.execRecursively(obj, ['function'], utils.patchToString)\\n}\",redirectToString:\"(proxyObj, originalObj) => {\\n  const handler = {\\n    apply: function (target, ctx) {\\n      // This fixes e.g. ` + \"`\" + `HTMLMediaElement.prototype.canPlayType.toString + \\\"\\\"` + \"`\" + `\\n      if (ctx === Function.prototype.toString) {\\n        return utils.makeNativeString('toString')\\n      }\\n\\n      // ` + \"`\" + `toString` + \"`\" + ` targeted at our proxied Object detected\\n      if (ctx === proxyObj) {\\n        const fallback = () =>\\n          originalObj && originalObj.name\\n            ? utils.makeNativeString(originalObj.name)\\n            : utils.makeNativeString(proxyObj.name)\\n\\n        // Return the toString representation of our original object if possible\\n        return originalObj + '' || fallback()\\n      }\\n\\n      if (typeof ctx === 'undefined' || ctx === null) {\\n        return target.call(ctx)\\n      }\\n\\n      // Check if the toString protype of the context is the same as the global prototype,\\n      // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` + \"`\" + ` test case\\n      const hasSameProto = Object.getPrototypeOf(\\n        Function.prototype.toString\\n      ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins\\n      if (!hasSameProto) {\\n        // Pass the call on to the local Function.prototype.toString instead\\n        return ctx.toString()\\n      }\\n\\n      return target.call(ctx)\\n    }\\n  }\\n\\n  const toStringProxy = new Proxy(\\n    Function.prototype.toString,\\n    utils.stripProxyFromErrors(handler)\\n  )\\n  utils.replaceProperty(Function.prototype, 'toString', {\\n    value: toStringProxy\\n  })\\n}\",replaceWithProxy:\"(obj, propName, handler) => {\\n  const originalObj = obj[propName]\\n  const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.redirectToString(proxyObj, originalObj)\\n\\n  return true\\n}\",replaceGetterWithProxy:\"(obj, propName, handler) => {\\n  const fn = Object.getOwnPropertyDescriptor(obj, propName).get\\n  const fnStr = fn.toString() // special getter function string\\n  const proxyObj = new Proxy(fn, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { get: proxyObj })\\n  utils.patchToString(proxyObj, fnStr)\\n\\n  return true\\n}\",replaceGetterSetter:\"(obj, propName, handlerGetterSetter) => {\\n  const ownPropertyDescriptor = Object.getOwnPropertyDescriptor(obj, propName)\\n  const handler = { ...ownPropertyDescriptor }\\n\\n  if (handlerGetterSetter.get !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.get\\n    handler.get = function() {\\n      return handlerGetterSetter.get.call(this, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.get, nativeFn)\\n  }\\n\\n  if (handlerGetterSetter.set !== undefined) {\\n    const nativeFn = ownPropertyDescriptor.set\\n    handler.set = function(newValue) {\\n      handlerGetterSetter.set.call(this, newValue, nativeFn.bind(this))\\n    }\\n    utils.redirectToString(handler.set, nativeFn)\\n  }\\n\\n  Object.defineProperty(obj, propName, handler)\\n}\",mockWithProxy:\"(obj, propName, pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n\\n  utils.replaceProperty(obj, propName, { value: proxyObj })\\n  utils.patchToString(proxyObj)\\n\\n  return true\\n}\",createProxy:\"(pseudoTarget, handler) => {\\n  const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler))\\n  utils.patchToString(proxyObj)\\n\\n  return proxyObj\\n}\",splitObjPath:\"objPath => ({\\n  // Remove last dot entry (property) ==> ` + \"`\" + `HTMLMediaElement.prototype` + \"`\" + `\\n  objName: objPath.split('.').slice(0, -1).join('.'),\\n  // Extract last dot entry ==> ` + \"`\" + `canPlayType` + \"`\" + `\\n  propName: objPath.split('.').slice(-1)[0]\\n})\",replaceObjPathWithProxy:\"(objPath, handler) => {\\n  const { objName, propName } = utils.splitObjPath(objPath)\\n  const obj = eval(objName) // eslint-disable-line no-eval\\n  return utils.replaceWithProxy(obj, propName, handler)\\n}\",execRecursively:\"(obj = {}, typeFilter = [], fn) => {\\n  function recurse(obj) {\\n    for (const key in obj) {\\n      if (obj[key] === undefined) {\\n        continue\\n      }\\n      if (obj[key] && typeof obj[key] === 'object') {\\n        recurse(obj[key])\\n      } else {\\n        if (obj[key] && typeFilter.includes(typeof obj[key])) {\\n          fn.call(this, obj[key])\\n        }\\n      }\\n    }\\n  }\\n  recurse(obj)\\n  return obj\\n}\",stringifyFns:\"(fnObj = { hello: () => 'world' }) => {\\n  // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine\\n  // https://github.com/feross/fromentries\\n  function fromEntries(iterable) {\\n    return [...iterable].reduce((obj, [key, val]) => {\\n      obj[key] = val\\n      return obj\\n    }, {})\\n  }\\n  return (Object.fromEntries || fromEntries)(\\n    Object.entries(fnObj)\\n      .filter(([key, value]) => typeof value === 'function')\\n      .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval\\n  )\\n}\",materializeFns:\"(fnStrObj = { hello: \\\"() => 'world'\\\" }) => {\\n  return Object.fromEntries(\\n    Object.entries(fnStrObj).map(([key, value]) => {\\n      if (value.startsWith('function')) {\\n        // some trickery is needed to make oldschool functions work :-)\\n        return [key, eval(` + \"`\" + `() => ${value}` + \"`\" + `)()] // eslint-disable-line no-eval\\n      } else {\\n        // arrow functions just work\\n        return [key, eval(value)] // eslint-disable-line no-eval\\n      }\\n    })\\n  )\\n}\",makeHandler:\"() => ({\\n  // Used by simple ` + \"`\" + `navigator` + \"`\" + ` getter evasions\\n  getterValue: value => ({\\n    apply(target, ctx, args) {\\n      // Let's fetch the value first, to trigger and escalate potential errors\\n      // Illegal invocations like ` + \"`\" + `navigator.__proto__.vendor` + \"`\" + ` will throw here\\n      utils.cache.Reflect.apply(...arguments)\\n      return value\\n    }\\n  })\\n})\",arrayEquals:\"(array1, array2) => {\\n  if (array1.length !== array2.length) {\\n    return false\\n  }\\n  for (let i = 0; i < array1.length; ++i) {\\n    if (array1[i] !== array2[i]) {\\n      return false\\n    }\\n  }\\n  return true\\n}\",memoize:\"fn => {\\n  const cache = []\\n  return function(...args) {\\n    if (!cache.some(c => utils.arrayEquals(c.key, args))) {\\n      cache.push({ key: args, value: fn.apply(this, args) })\\n    }\\n    return cache.find(c => utils.arrayEquals(c.key, args)).value\\n  }\\n}\"},_mainFunction:\"(utils, opts) => {\\n      try {\\n        // Adds a contentWindow proxy to the provided iframe element\\n        const addContentWindowProxy = iframe => {\\n          const contentWindowProxy = {\\n            get(target, key) {\\n              // Now to the interesting part:\\n              // We actually make this thing behave like a regular iframe window,\\n              // by intercepting calls to e.g. ` + \"`\" + `.self` + \"`\" + ` and redirect it to the correct thing. :)\\n              // That makes it possible for these assertions to be correct:\\n              // iframe.contentWindow.self === window.top // must be false\\n              if (key === 'self') {\\n                return this\\n              }\\n              // iframe.contentWindow.frameElement === iframe // must be true\\n              if (key === 'frameElement') {\\n                return iframe\\n              }\\n              // Intercept iframe.contentWindow[0] to hide the property 0 added by the proxy.\\n              if (key === '0') {\\n                return undefined\\n              }\\n              return Reflect.get(target, key)\\n            }\\n          }\\n\\n          if (!iframe.contentWindow) {\\n            const proxy = new Proxy(window, contentWindowProxy)\\n            Object.defineProperty(iframe, 'contentWindow', {\\n              get() {\\n                return proxy\\n              },\\n              set(newValue) {\\n                return newValue // contentWindow is immutable\\n              },\\n              enumerable: true,\\n              configurable: false\\n            })\\n          }\\n        }\\n\\n        // Handles iframe element creation, augments ` + \"`\" + `srcdoc` + \"`\" + ` property so we can intercept further\\n        const handleIframeCreation = (target, thisArg, args) => {\\n          const iframe = target.apply(thisArg, args)\\n\\n          // We need to keep the originals around\\n          const _iframe = iframe\\n          const _srcdoc = _iframe.srcdoc\\n\\n          // Add hook for the srcdoc property\\n          // We need to be very surgical here to not break other iframes by accident\\n          Object.defineProperty(iframe, 'srcdoc', {\\n            configurable: true, // Important, so we can reset this later\\n            get: function() {\\n              return _srcdoc\\n            },\\n            set: function(newValue) {\\n              addContentWindowProxy(this)\\n              // Reset property, the hook is only needed once\\n              Object.defineProperty(iframe, 'srcdoc', {\\n                configurable: false,\\n                writable: false,\\n                value: _srcdoc\\n              })\\n              _iframe.srcdoc = newValue\\n            }\\n          })\\n          return iframe\\n        }\\n\\n        // Adds a hook to intercept iframe creation events\\n        const addIframeCreationSniffer = () => {\\n          /* global document */\\n          const createElementHandler = {\\n            // Make toString() native\\n            get(target, key) {\\n              return Reflect.get(target, key)\\n            },\\n            apply: function(target, thisArg, args) {\\n              const isIframe =\\n                args && args.length && ` + \"`\" + `${args[0]}` + \"`\" + `.toLowerCase() === 'iframe'\\n              if (!isIframe) {\\n                // Everything as usual\\n                return target.apply(thisArg, args)\\n              } else {\\n                return handleIframeCreation(target, thisArg, args)\\n              }\\n            }\\n          }\\n          // All this just due to iframes with srcdoc bug\\n          utils.replaceWithProxy(\\n            document,\\n            'createElement',\\n            createElementHandler\\n          )\\n        }\\n\\n        // Let's go\\n        addIframeCreationSniffer()\\n      } catch (err) {\\n        // console.warn(err)\\n      }\\n    }\",_args:[]});\n})();`\n"
  },
  {
    "path": "pkg/engine/headless/captcha/capsolver/capsolver.go",
    "content": "package capsolver\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/captcha\"\n)\n\nconst (\n\tdefaultPollInterval = 3 * time.Second\n\tdefaultTimeout      = 120 * time.Second\n)\n\nvar baseURL = \"https://api.capsolver.com\"\n\nfunc SetBaseURL(url string) {\n\tbaseURL = url\n}\n\nvar taskTypes = map[captcha.Provider]string{\n\tcaptcha.ProviderRecaptchaV2:           \"ReCaptchaV2TaskProxyLess\",\n\tcaptcha.ProviderRecaptchaV3:           \"ReCaptchaV3TaskProxyLess\",\n\tcaptcha.ProviderRecaptchaV2Enterprise: \"ReCaptchaV2EnterpriseTaskProxyLess\",\n\tcaptcha.ProviderRecaptchaV3Enterprise: \"ReCaptchaV3EnterpriseTaskProxyLess\",\n\tcaptcha.ProviderTurnstile:             \"AntiTurnstileTaskProxyLess\",\n\tcaptcha.ProviderHCaptcha:              \"HCaptchaTaskProxyLess\",\n}\n\ntype Solver struct {\n\tapiKey string\n\tclient *http.Client\n}\n\nfunc init() {\n\tcaptcha.RegisterSolver(\"capsolver\", func(apiKey string) (captcha.Solver, error) {\n\t\treturn New(apiKey), nil\n\t})\n}\n\nfunc New(apiKey string) *Solver {\n\treturn &Solver{\n\t\tapiKey: apiKey,\n\t\tclient: &http.Client{Timeout: 30 * time.Second},\n\t}\n}\n\nfunc (s *Solver) Solve(ctx context.Context, info *captcha.Info) (*captcha.Solution, error) {\n\ttaskType, ok := taskTypes[info.Provider]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported captcha provider for capsolver: %s\", info.Provider)\n\t}\n\n\ttask := map[string]any{\n\t\t\"type\":       taskType,\n\t\t\"websiteURL\": info.PageURL,\n\t\t\"websiteKey\": info.SiteKey,\n\t}\n\tif (info.Provider == captcha.ProviderRecaptchaV3 || info.Provider == captcha.ProviderRecaptchaV3Enterprise) && info.Action != \"\" {\n\t\ttask[\"pageAction\"] = info.Action\n\t}\n\n\ttaskID, err := s.createTask(ctx, task)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create task: %w\", err)\n\t}\n\n\treturn s.pollResult(ctx, taskID, info.Provider)\n}\n\ntype createTaskRequest struct {\n\tClientKey string         `json:\"clientKey\"`\n\tTask      map[string]any `json:\"task\"`\n}\n\ntype createTaskResponse struct {\n\tErrorID          int    `json:\"errorId\"`\n\tErrorCode        string `json:\"errorCode\"`\n\tErrorDescription string `json:\"errorDescription\"`\n\tTaskID           string `json:\"taskId\"`\n}\n\ntype getTaskResultRequest struct {\n\tClientKey string `json:\"clientKey\"`\n\tTaskID    string `json:\"taskId\"`\n}\n\ntype getTaskResultResponse struct {\n\tErrorID          int            `json:\"errorId\"`\n\tErrorCode        string `json:\"errorCode\"`\n\tErrorDescription string `json:\"errorDescription\"`\n\tStatus           string         `json:\"status\"`\n\tSolution         map[string]any `json:\"solution\"`\n}\n\nfunc (s *Solver) createTask(ctx context.Context, task map[string]any) (string, error) {\n\tbody, err := json.Marshal(createTaskRequest{\n\t\tClientKey: s.apiKey,\n\t\tTask:      task,\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+\"/createTask\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar result createTaskResponse\n\tif err := json.Unmarshal(respBody, &result); err != nil {\n\t\treturn \"\", err\n\t}\n\tif result.ErrorID != 0 {\n\t\treturn \"\", fmt.Errorf(\"capsolver error %s: %s\", result.ErrorCode, result.ErrorDescription)\n\t}\n\treturn result.TaskID, nil\n}\n\nfunc (s *Solver) pollResult(ctx context.Context, taskID string, provider captcha.Provider) (*captcha.Solution, error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultTimeout)\n\tdefer cancel()\n\n\tticker := time.NewTicker(defaultPollInterval)\n\tdefer ticker.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, fmt.Errorf(\"captcha solve timed out after %s\", defaultTimeout)\n\t\tcase <-ticker.C:\n\t\t\tresult, err := s.getTaskResult(ctx, taskID)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif result.ErrorID != 0 {\n\t\t\t\treturn nil, fmt.Errorf(\"capsolver error %s: %s\", result.ErrorCode, result.ErrorDescription)\n\t\t\t}\n\t\t\tif result.Status != \"ready\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn extractToken(result.Solution, provider)\n\t\t}\n\t}\n}\n\nfunc (s *Solver) getTaskResult(ctx context.Context, taskID string) (*getTaskResultResponse, error) {\n\tbody, err := json.Marshal(getTaskResultRequest{\n\t\tClientKey: s.apiKey,\n\t\tTaskID:    taskID,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+\"/getTaskResult\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := s.client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar result getTaskResultResponse\n\tif err := json.Unmarshal(respBody, &result); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &result, nil\n}\n\nfunc extractToken(solution map[string]any, provider captcha.Provider) (*captcha.Solution, error) {\n\t// capsolver returns turnstile tokens under \"token\", everything else under \"gRecaptchaResponse\"\n\tkey := \"gRecaptchaResponse\"\n\tif provider == captcha.ProviderTurnstile {\n\t\tkey = \"token\"\n\t}\n\n\ttoken, ok := solution[key].(string)\n\tif !ok || token == \"\" {\n\t\treturn nil, fmt.Errorf(\"no token found in capsolver solution (key=%s)\", key)\n\t}\n\n\treturn &captcha.Solution{Token: token, Provider: provider}, nil\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/capsolver/capsolver_test.go",
    "content": "package capsolver\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"sync/atomic\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/captcha\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSolve(t *testing.T) {\n\tvar pollCount atomic.Int32\n\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tvar body map[string]any\n\t\t_ = json.NewDecoder(r.Body).Decode(&body)\n\n\t\tswitch r.URL.Path {\n\t\tcase \"/createTask\":\n\t\t\ttask, ok := body[\"task\"].(map[string]any)\n\t\t\tassert.True(t, ok)\n\t\t\tassert.Equal(t, \"ReCaptchaV2TaskProxyLess\", task[\"type\"])\n\t\t\tassert.Equal(t, \"https://example.com\", task[\"websiteURL\"])\n\t\t\tassert.Equal(t, \"test-sitekey\", task[\"websiteKey\"])\n\n\t\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\t\"errorId\": 0,\n\t\t\t\t\"taskId\":  \"task-123\",\n\t\t\t})\n\n\t\tcase \"/getTaskResult\":\n\t\t\tassert.Equal(t, \"task-123\", body[\"taskId\"])\n\n\t\t\tcount := pollCount.Add(1)\n\t\t\tif count < 2 {\n\t\t\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\t\t\"errorId\": 0,\n\t\t\t\t\t\"status\":  \"processing\",\n\t\t\t\t})\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\t\"errorId\": 0,\n\t\t\t\t\"status\":  \"ready\",\n\t\t\t\t\"solution\": map[string]any{\n\t\t\t\t\t\"gRecaptchaResponse\": \"solved-token-abc\",\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\torigURL := baseURL\n\tSetBaseURL(server.URL)\n\tdefer SetBaseURL(origURL)\n\n\tcs := New(\"test-api-key\")\n\tsolution, err := cs.Solve(context.Background(), &captcha.Info{\n\t\tProvider: captcha.ProviderRecaptchaV2,\n\t\tSiteKey:  \"test-sitekey\",\n\t\tPageURL:  \"https://example.com\",\n\t})\n\n\trequire.NoError(t, err)\n\trequire.NotNil(t, solution)\n\tassert.Equal(t, \"solved-token-abc\", solution.Token)\n\tassert.Equal(t, captcha.ProviderRecaptchaV2, solution.Provider)\n\tassert.GreaterOrEqual(t, int(pollCount.Load()), 2)\n}\n\nfunc TestCreateTaskError(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\"errorId\":          1,\n\t\t\t\"errorCode\":        \"ERROR_KEY_DENIED\",\n\t\t\t\"errorDescription\": \"Account not found or blocked\",\n\t\t})\n\t}))\n\tdefer server.Close()\n\n\torigURL := baseURL\n\tSetBaseURL(server.URL)\n\tdefer SetBaseURL(origURL)\n\n\tcs := New(\"bad-key\")\n\t_, err := cs.Solve(context.Background(), &captcha.Info{\n\t\tProvider: captcha.ProviderRecaptchaV2,\n\t\tSiteKey:  \"key\",\n\t\tPageURL:  \"https://example.com\",\n\t})\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"ERROR_KEY_DENIED\")\n}\n\nfunc TestTurnstileToken(t *testing.T) {\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tswitch r.URL.Path {\n\t\tcase \"/createTask\":\n\t\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\t\"errorId\": 0,\n\t\t\t\t\"taskId\":  \"task-turnstile\",\n\t\t\t})\n\t\tcase \"/getTaskResult\":\n\t\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\t\"errorId\": 0,\n\t\t\t\t\"status\":  \"ready\",\n\t\t\t\t\"solution\": map[string]any{\n\t\t\t\t\t\"token\": \"turnstile-token-xyz\",\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t}))\n\tdefer server.Close()\n\n\torigURL := baseURL\n\tSetBaseURL(server.URL)\n\tdefer SetBaseURL(origURL)\n\n\tcs := New(\"key\")\n\tsolution, err := cs.Solve(context.Background(), &captcha.Info{\n\t\tProvider: captcha.ProviderTurnstile,\n\t\tSiteKey:  \"cf-key\",\n\t\tPageURL:  \"https://example.com\",\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"turnstile-token-xyz\", solution.Token)\n\tassert.Equal(t, captcha.ProviderTurnstile, solution.Provider)\n}\n\nfunc TestExtractToken(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tsolution map[string]any\n\t\tprovider captcha.Provider\n\t\twant     string\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"recaptcha v2\",\n\t\t\tsolution: map[string]any{\"gRecaptchaResponse\": \"token-a\"},\n\t\t\tprovider: captcha.ProviderRecaptchaV2,\n\t\t\twant:     \"token-a\",\n\t\t},\n\t\t{\n\t\t\tname:     \"turnstile\",\n\t\t\tsolution: map[string]any{\"token\": \"token-b\"},\n\t\t\tprovider: captcha.ProviderTurnstile,\n\t\t\twant:     \"token-b\",\n\t\t},\n\t\t{\n\t\t\tname:     \"missing token\",\n\t\t\tsolution: map[string]any{},\n\t\t\tprovider: captcha.ProviderRecaptchaV2,\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"empty token\",\n\t\t\tsolution: map[string]any{\"gRecaptchaResponse\": \"\"},\n\t\t\tprovider: captcha.ProviderHCaptcha,\n\t\t\twantErr:  true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tsol, err := extractToken(tt.solution, tt.provider)\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tt.want, sol.Token)\n\t\t})\n\t}\n}\n\nfunc TestUnsupportedProvider(t *testing.T) {\n\tcs := New(\"key\")\n\t_, err := cs.Solve(context.Background(), &captcha.Info{\n\t\tProvider: \"unknown\",\n\t\tSiteKey:  \"key\",\n\t\tPageURL:  \"https://example.com\",\n\t})\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"unsupported\")\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/captcha.go",
    "content": "package captcha\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/go-rod/rod\"\n\tditcaptcha \"github.com/happyhackingspace/dit/captcha\"\n\t\"github.com/projectdiscovery/gologger\"\n\tcaptchajs \"github.com/projectdiscovery/katana/pkg/engine/headless/captcha/js\"\n)\n\ntype Handler struct {\n\tsolver Solver\n}\n\nfunc NewHandler(solverProvider, apiKey string) (*Handler, error) {\n\tsolver, err := NewSolver(solverProvider, apiKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"captcha solver init: %w\", err)\n\t}\n\n\treturn &Handler{\n\t\tsolver: solver,\n\t}, nil\n}\n\nfunc (h *Handler) HandleIfCaptcha(ctx context.Context, page *rod.Page, pageHTML string) (bool, error) {\n\tif ditcaptcha.DetectCaptchaInHTML(pageHTML) == ditcaptcha.CaptchaTypeNone && !strings.Contains(pageHTML, \"data-sitekey\") {\n\t\treturn false, nil\n\t}\n\n\tinfo, err := Identify(page)\n\tif err != nil {\n\t\tgologger.Debug().Msgf(\"captcha identification failed: %s\", err)\n\t}\n\n\tif info == nil {\n\t\treturn false, nil\n\t}\n\n\treturn h.solveCaptcha(ctx, page, info)\n}\n\nfunc (h *Handler) solveCaptcha(ctx context.Context, page *rod.Page, info *Info) (bool, error) {\n\tgologger.Debug().Msgf(\"captcha detected: provider=%s sitekey=%s url=%s\", info.Provider, info.SiteKey, info.PageURL)\n\n\tsolution, err := h.solver.Solve(ctx, info)\n\tif err != nil {\n\t\treturn true, fmt.Errorf(\"captcha solve: %w\", err)\n\t}\n\n\tgologger.Debug().Msgf(\"captcha solved, injecting token: provider=%s\", solution.Provider)\n\n\tif err := injectToken(page, solution); err != nil {\n\t\treturn true, fmt.Errorf(\"captcha inject: %w\", err)\n\t}\n\n\treturn true, nil\n}\n\nfunc injectToken(page *rod.Page, solution *Solution) error {\n\tjs, err := injectionScript(solution.Provider)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = page.Eval(js, solution.Token)\n\treturn err\n}\n\nfunc injectionScript(provider Provider) (string, error) {\n\tswitch provider {\n\tcase ProviderRecaptchaV2, ProviderRecaptchaV3, ProviderRecaptchaV2Enterprise, ProviderRecaptchaV3Enterprise:\n\t\treturn captchajs.InjectRecaptchaJS, nil\n\tcase ProviderTurnstile:\n\t\treturn captchajs.InjectTurnstileJS, nil\n\tcase ProviderHCaptcha:\n\t\treturn captchajs.InjectHCaptchaJS, nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unsupported captcha provider for injection: %s\", provider)\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/helpers_test.go",
    "content": "package captcha\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/launcher\"\n)\n\nfunc setupBrowser(t *testing.T) *rod.Browser {\n\tt.Helper()\n\tpath, found := launcher.LookPath()\n\tif !found {\n\t\tt.Skip(\"chromium not found, skipping browser tests\")\n\t}\n\tu := launcher.New().Bin(path).Headless(true).Leakless(true).MustLaunch()\n\tbrowser := rod.New().ControlURL(u).MustConnect()\n\tt.Cleanup(func() { browser.MustClose() })\n\treturn browser\n}\n\nfunc servePage(t *testing.T, html string) string {\n\tt.Helper()\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t_, _ = fmt.Fprint(w, html)\n\t}))\n\tt.Cleanup(server.Close)\n\treturn server.URL\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/identify.go",
    "content": "package captcha\n\nimport (\n\t\"github.com/go-rod/rod\"\n\tcaptchajs \"github.com/projectdiscovery/katana/pkg/engine/headless/captcha/js\"\n)\n\ntype Provider string\n\nconst (\n\tProviderRecaptchaV2           Provider = \"recaptchav2\"\n\tProviderRecaptchaV3           Provider = \"recaptchav3\"\n\tProviderRecaptchaV2Enterprise Provider = \"recaptchav2enterprise\"\n\tProviderRecaptchaV3Enterprise Provider = \"recaptchav3enterprise\"\n\tProviderTurnstile             Provider = \"turnstile\"\n\tProviderHCaptcha              Provider = \"hcaptcha\"\n)\n\ntype Info struct {\n\tProvider Provider\n\tSiteKey  string\n\tPageURL  string\n\tAction   string\n}\n\nfunc Identify(page *rod.Page) (*Info, error) {\n\tpageURL, err := page.Eval(\"() => window.location.href\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresult, err := page.Eval(captchajs.IdentifyJS)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif result.Value.Nil() {\n\t\treturn nil, nil\n\t}\n\n\treturn &Info{\n\t\tProvider: Provider(result.Value.Get(\"provider\").Str()),\n\t\tSiteKey:  result.Value.Get(\"sitekey\").Str(),\n\t\tPageURL:  pageURL.Value.Str(),\n\t\tAction:   result.Value.Get(\"action\").Str(),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/identify_test.go",
    "content": "package captcha\n\nimport (\n\t\"testing\"\n\n\tditcaptcha \"github.com/happyhackingspace/dit/captcha\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDetectCaptchaWithDit(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\thtml string\n\t\twant ditcaptcha.CaptchaType\n\t}{\n\t\t{\n\t\t\tname: \"recaptcha\",\n\t\t\thtml: `<html><body><div class=\"g-recaptcha\" data-sitekey=\"6Lc\"></div>\n\t\t\t\t<script src=\"https://www.google.com/recaptcha/api.js\"></script></body></html>`,\n\t\t\twant: ditcaptcha.CaptchaTypeRecaptcha,\n\t\t},\n\t\t{\n\t\t\tname: \"turnstile\",\n\t\t\thtml: `<html><body><div class=\"cf-turnstile\" data-sitekey=\"0x4AAA\"></div>\n\t\t\t\t<script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\"></script></body></html>`,\n\t\t\twant: ditcaptcha.CaptchaTypeTurnstile,\n\t\t},\n\t\t{\n\t\t\tname: \"hcaptcha\",\n\t\t\thtml: `<html><body><div class=\"h-captcha\" data-sitekey=\"abc\"></div>\n\t\t\t\t<script src=\"https://js.hcaptcha.com/1/api.js\"></script></body></html>`,\n\t\t\twant: ditcaptcha.CaptchaTypeHCaptcha,\n\t\t},\n\t\t{\n\t\t\tname: \"no captcha\",\n\t\t\thtml: `<html><body><h1>Hello</h1></body></html>`,\n\t\t\twant: ditcaptcha.CaptchaTypeNone,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := ditcaptcha.DetectCaptchaInHTML(tt.html)\n\t\t\tassert.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n\nfunc TestIdentify(t *testing.T) {\n\tbrowser := setupBrowser(t)\n\n\ttests := []struct {\n\t\tname         string\n\t\thtml         string\n\t\twantProvider Provider\n\t\twantSiteKey  string\n\t\twantNil      bool\n\t}{\n\t\t{\n\t\t\tname: \"recaptcha v2\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<div class=\"g-recaptcha\" data-sitekey=\"6LcXrecapv2\"></div>\n\t\t\t\t<script src=\"https://www.google.com/recaptcha/api.js\" async defer></script>\n\t\t\t</body></html>`,\n\t\t\twantProvider: ProviderRecaptchaV2,\n\t\t\twantSiteKey:  \"6LcXrecapv2\",\n\t\t},\n\t\t{\n\t\t\tname: \"recaptcha v3\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<script src=\"https://www.google.com/recaptcha/api.js?render=6LcXrecapv3\"></script>\n\t\t\t</body></html>`,\n\t\t\twantProvider: ProviderRecaptchaV3,\n\t\t\twantSiteKey:  \"6LcXrecapv3\",\n\t\t},\n\t\t{\n\t\t\tname: \"cloudflare turnstile\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<div class=\"cf-turnstile\" data-sitekey=\"0x4AAATURNSTILE\"></div>\n\t\t\t\t<script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\" async defer></script>\n\t\t\t</body></html>`,\n\t\t\twantProvider: ProviderTurnstile,\n\t\t\twantSiteKey:  \"0x4AAATURNSTILE\",\n\t\t},\n\t\t{\n\t\t\tname: \"hcaptcha\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<div class=\"h-captcha\" data-sitekey=\"hcap-sitekey-123\"></div>\n\t\t\t\t<script src=\"https://js.hcaptcha.com/1/api.js\" async defer></script>\n\t\t\t</body></html>`,\n\t\t\twantProvider: ProviderHCaptcha,\n\t\t\twantSiteKey:  \"hcap-sitekey-123\",\n\t\t},\n\t\t{\n\t\t\tname: \"no captcha\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<h1>Hello World</h1>\n\t\t\t\t<form><input type=\"text\" name=\"q\"><button>Search</button></form>\n\t\t\t</body></html>`,\n\t\t\twantNil: true,\n\t\t},\n\t\t{\n\t\t\tname: \"recaptcha v2 enterprise\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<div class=\"g-recaptcha\" data-sitekey=\"6LcEntV2\"></div>\n\t\t\t\t<script src=\"https://www.google.com/recaptcha/enterprise.js\" async defer></script>\n\t\t\t</body></html>`,\n\t\t\twantProvider: ProviderRecaptchaV2Enterprise,\n\t\t\twantSiteKey:  \"6LcEntV2\",\n\t\t},\n\t\t{\n\t\t\tname: \"recaptcha v3 enterprise\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<script src=\"https://www.google.com/recaptcha/enterprise.js?render=6LcEntV3\"></script>\n\t\t\t</body></html>`,\n\t\t\twantProvider: ProviderRecaptchaV3Enterprise,\n\t\t\twantSiteKey:  \"6LcEntV3\",\n\t\t},\n\t\t{\n\t\t\tname: \"generic data-sitekey fallback\",\n\t\t\thtml: `<html><body>\n\t\t\t\t<div id=\"captcha-widget\" data-sitekey=\"generic-key-456\"></div>\n\t\t\t</body></html>`,\n\t\t\twantProvider: ProviderRecaptchaV2,\n\t\t\twantSiteKey:  \"generic-key-456\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\turl := servePage(t, tt.html)\n\t\t\tpage := browser.MustPage(url)\n\t\t\tdefer page.MustClose()\n\t\t\tpage.MustWaitLoad()\n\n\t\t\tinfo, err := Identify(page)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif tt.wantNil {\n\t\t\t\tassert.Nil(t, info)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\trequire.NotNil(t, info)\n\t\t\tassert.Equal(t, tt.wantProvider, info.Provider)\n\t\t\tassert.Equal(t, tt.wantSiteKey, info.SiteKey)\n\t\t\tassert.Contains(t, info.PageURL, url)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/inject_test.go",
    "content": "package captcha\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestInjectionScript(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tprovider Provider\n\t\twantErr  bool\n\t\tcontains string\n\t}{\n\t\t{\n\t\t\tname:     \"recaptcha v2\",\n\t\t\tprovider: ProviderRecaptchaV2,\n\t\t\tcontains: \"g-recaptcha-response\",\n\t\t},\n\t\t{\n\t\t\tname:     \"recaptcha v3\",\n\t\t\tprovider: ProviderRecaptchaV3,\n\t\t\tcontains: \"g-recaptcha-response\",\n\t\t},\n\t\t{\n\t\t\tname:     \"recaptcha v2 enterprise\",\n\t\t\tprovider: ProviderRecaptchaV2Enterprise,\n\t\t\tcontains: \"g-recaptcha-response\",\n\t\t},\n\t\t{\n\t\t\tname:     \"recaptcha v3 enterprise\",\n\t\t\tprovider: ProviderRecaptchaV3Enterprise,\n\t\t\tcontains: \"g-recaptcha-response\",\n\t\t},\n\t\t{\n\t\t\tname:     \"turnstile\",\n\t\t\tprovider: ProviderTurnstile,\n\t\t\tcontains: \"cf-turnstile-response\",\n\t\t},\n\t\t{\n\t\t\tname:     \"hcaptcha\",\n\t\t\tprovider: ProviderHCaptcha,\n\t\t\tcontains: \"h-captcha-response\",\n\t\t},\n\t\t{\n\t\t\tname:     \"unknown provider\",\n\t\t\tprovider: \"unknown\",\n\t\t\twantErr:  true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tjs, err := injectionScript(tt.provider)\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Contains(t, js, tt.contains)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/injection_test.go",
    "content": "package captcha\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestE2E_RecaptchaV2_Injection(t *testing.T) {\n\tbrowser := setupBrowser(t)\n\n\treceived := make(chan string, 1)\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method == \"POST\" {\n\t\t\t_ = r.ParseForm()\n\t\t\treceived <- r.FormValue(\"g-recaptcha-response\")\n\t\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t\t_, _ = w.Write([]byte(`<html><body><div id=\"done\">OK</div></body></html>`))\n\t\t\treturn\n\t\t}\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t_, _ = w.Write([]byte(`<html><body>\n\t\t\t<form method=\"POST\">\n\t\t\t\t<div class=\"g-recaptcha\" data-sitekey=\"6LcTestKey\" data-callback=\"onSolved\"></div>\n\t\t\t\t<textarea id=\"g-recaptcha-response\" name=\"g-recaptcha-response\" style=\"display:none\"></textarea>\n\t\t\t</form>\n\t\t\t<div id=\"result\"></div>\n\t\t\t<script>function onSolved(token) { document.getElementById('result').textContent = 'SOLVED:' + token; }</script>\n\t\t</body></html>`))\n\t}))\n\tt.Cleanup(server.Close)\n\n\tp := browser.MustPage(server.URL)\n\tdefer p.MustClose()\n\tp.MustWaitLoad()\n\n\tinfo, err := Identify(p)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, info)\n\tassert.Equal(t, ProviderRecaptchaV2, info.Provider)\n\tassert.Equal(t, \"6LcTestKey\", info.SiteKey)\n\n\terr = injectToken(p, &Solution{Token: \"test-token-abc\", Provider: ProviderRecaptchaV2})\n\trequire.NoError(t, err)\n\n\tp.MustWaitLoad()\n\ttoken := <-received\n\tassert.Equal(t, \"test-token-abc\", token)\n}\n\nfunc TestE2E_Turnstile_Injection(t *testing.T) {\n\tbrowser := setupBrowser(t)\n\n\treceived := make(chan string, 1)\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method == \"POST\" {\n\t\t\t_ = r.ParseForm()\n\t\t\treceived <- r.FormValue(\"cf-turnstile-response\")\n\t\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t\t_, _ = w.Write([]byte(`<html><body><div id=\"done\">OK</div></body></html>`))\n\t\t\treturn\n\t\t}\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t_, _ = w.Write([]byte(`<html><body>\n\t\t\t<form method=\"POST\">\n\t\t\t\t<div class=\"cf-turnstile\" data-sitekey=\"0x4AAATurnstileKey\" data-callback=\"onTurnstile\"></div>\n\t\t\t\t<input type=\"hidden\" name=\"cf-turnstile-response\" value=\"\">\n\t\t\t</form>\n\t\t\t<div id=\"result\"></div>\n\t\t\t<script>function onTurnstile(token) { document.getElementById('result').textContent = 'TURNSTILE:' + token; }</script>\n\t\t</body></html>`))\n\t}))\n\tt.Cleanup(server.Close)\n\n\tp := browser.MustPage(server.URL)\n\tdefer p.MustClose()\n\tp.MustWaitLoad()\n\n\tinfo, err := Identify(p)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, info)\n\tassert.Equal(t, ProviderTurnstile, info.Provider)\n\tassert.Equal(t, \"0x4AAATurnstileKey\", info.SiteKey)\n\n\terr = injectToken(p, &Solution{Token: \"test-token-def\", Provider: ProviderTurnstile})\n\trequire.NoError(t, err)\n\n\tp.MustWaitLoad()\n\ttoken := <-received\n\tassert.Equal(t, \"test-token-def\", token)\n}\n\nfunc TestE2E_HCaptcha_Injection(t *testing.T) {\n\tbrowser := setupBrowser(t)\n\n\treceived := make(chan map[string]string, 1)\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method == \"POST\" {\n\t\t\t_ = r.ParseForm()\n\t\t\treceived <- map[string]string{\n\t\t\t\t\"h-captcha-response\":   r.FormValue(\"h-captcha-response\"),\n\t\t\t\t\"g-recaptcha-response\": r.FormValue(\"g-recaptcha-response\"),\n\t\t\t}\n\t\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t\t_, _ = w.Write([]byte(`<html><body><div id=\"done\">OK</div></body></html>`))\n\t\t\treturn\n\t\t}\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t_, _ = w.Write([]byte(`<html><body>\n\t\t\t<form method=\"POST\">\n\t\t\t\t<div class=\"h-captcha\" data-sitekey=\"hcap-test-key\" data-callback=\"onHCaptcha\"></div>\n\t\t\t\t<textarea name=\"h-captcha-response\" style=\"display:none\"></textarea>\n\t\t\t\t<textarea name=\"g-recaptcha-response\" style=\"display:none\"></textarea>\n\t\t\t</form>\n\t\t\t<div id=\"result\"></div>\n\t\t\t<script>function onHCaptcha(token) { document.getElementById('result').textContent = 'HCAPTCHA:' + token; }</script>\n\t\t</body></html>`))\n\t}))\n\tt.Cleanup(server.Close)\n\n\tp := browser.MustPage(server.URL)\n\tdefer p.MustClose()\n\tp.MustWaitLoad()\n\n\tinfo, err := Identify(p)\n\trequire.NoError(t, err)\n\trequire.NotNil(t, info)\n\tassert.Equal(t, ProviderHCaptcha, info.Provider)\n\tassert.Equal(t, \"hcap-test-key\", info.SiteKey)\n\n\terr = injectToken(p, &Solution{Token: \"test-token-ghi\", Provider: ProviderHCaptcha})\n\trequire.NoError(t, err)\n\n\tp.MustWaitLoad()\n\tvals := <-received\n\tassert.Equal(t, \"test-token-ghi\", vals[\"h-captcha-response\"])\n\tassert.Equal(t, \"test-token-ghi\", vals[\"g-recaptcha-response\"])\n}\n\nfunc TestE2E_RecaptchaV2_FormSubmitFallback(t *testing.T) {\n\tbrowser := setupBrowser(t)\n\n\treceived := make(chan string, 1)\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method == \"POST\" {\n\t\t\t_ = r.ParseForm()\n\t\t\treceived <- r.FormValue(\"g-recaptcha-response\")\n\t\t\t_, _ = w.Write([]byte(\"OK\"))\n\t\t\treturn\n\t\t}\n\t\tw.Header().Set(\"Content-Type\", \"text/html\")\n\t\t_, _ = w.Write([]byte(`<html><body>\n\t\t\t<form action=\"` + r.Host + `\" method=\"POST\">\n\t\t\t\t<div class=\"g-recaptcha\" data-sitekey=\"6LcNoCallback\"></div>\n\t\t\t\t<textarea id=\"g-recaptcha-response\" name=\"g-recaptcha-response\" style=\"display:none\"></textarea>\n\t\t\t</form>\n\t\t</body></html>`))\n\t}))\n\tt.Cleanup(server.Close)\n\n\tp := browser.MustPage(server.URL)\n\tdefer p.MustClose()\n\tp.MustWaitLoad()\n\n\terr := injectToken(p, &Solution{Token: \"fallback-token\", Provider: ProviderRecaptchaV2})\n\trequire.NoError(t, err)\n\n\tp.MustWaitLoad()\n\ttoken := <-received\n\tassert.Equal(t, \"fallback-token\", token)\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/integration_test.go",
    "content": "package captcha\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIntegration_DetectRealCaptchaPages(t *testing.T) {\n\tif testing.Short() {\n\t\tt.Skip(\"skipping integration test in short mode\")\n\t}\n\n\tbrowser := setupBrowser(t)\n\n\ttests := []struct {\n\t\tname         string\n\t\turl          string\n\t\twantProvider Provider\n\t}{\n\t\t{\n\t\t\tname:         \"google recaptcha v2 demo\",\n\t\t\turl:          \"https://www.google.com/recaptcha/api2/demo\",\n\t\t\twantProvider: ProviderRecaptchaV2,\n\t\t},\n\t\t{\n\t\t\tname:         \"hcaptcha official demo\",\n\t\t\turl:          \"https://accounts.hcaptcha.com/demo\",\n\t\t\twantProvider: ProviderHCaptcha,\n\t\t},\n\t\t{\n\t\t\tname:         \"2captcha turnstile demo\",\n\t\t\turl:          \"https://2captcha.com/demo/cloudflare-turnstile\",\n\t\t\twantProvider: ProviderTurnstile,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tpage := browser.MustPage(tt.url)\n\t\t\tdefer page.MustClose()\n\t\t\tpage.MustWaitLoad()\n\n\t\t\tinfo, err := Identify(page)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.NotNil(t, info, \"expected captcha to be detected on %s\", tt.url)\n\n\t\t\tt.Logf(\"detected: provider=%s sitekey=%s url=%s\", info.Provider, info.SiteKey, info.PageURL)\n\n\t\t\tassert.Equal(t, tt.wantProvider, info.Provider)\n\t\t\tassert.NotEmpty(t, info.SiteKey)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/js/identify.js",
    "content": "() => {\n  const hcap = document.querySelector('.h-captcha[data-sitekey]');\n  if (hcap) {\n    return { provider: \"hcaptcha\", sitekey: hcap.getAttribute(\"data-sitekey\"), action: \"\" };\n  }\n\n  const cf = document.querySelector('.cf-turnstile[data-sitekey]');\n  if (cf) {\n    return { provider: \"turnstile\", sitekey: cf.getAttribute(\"data-sitekey\"), action: \"\" };\n  }\n\n  const recapScripts = document.querySelectorAll(\n    'script[src*=\"recaptcha/api.js\"], script[src*=\"recaptcha/enterprise.js\"]'\n  );\n  for (const s of recapScripts) {\n    try {\n      const u = new URL(s.src);\n      const isEnterprise = u.pathname.includes(\"/enterprise.js\");\n      const renderParam = u.searchParams.get(\"render\");\n      if (renderParam && renderParam !== \"explicit\") {\n        return {\n          provider: isEnterprise ? \"recaptchav3enterprise\" : \"recaptchav3\",\n          sitekey: renderParam,\n          action: \"\"\n        };\n      }\n    } catch {}\n  }\n\n  const isEnterprise = recapScripts.length > 0 &&\n    Array.from(recapScripts).some(s => s.src.includes(\"/enterprise.js\"));\n\n  const recap = document.querySelector('.g-recaptcha[data-sitekey]');\n  if (recap) {\n    return {\n      provider: isEnterprise ? \"recaptchav2enterprise\" : \"recaptchav2\",\n      sitekey: recap.getAttribute(\"data-sitekey\"),\n      action: \"\"\n    };\n  }\n\n  const generic = document.querySelector('[data-sitekey]');\n  if (generic) {\n    return {\n      provider: isEnterprise ? \"recaptchav2enterprise\" : \"recaptchav2\",\n      sitekey: generic.getAttribute(\"data-sitekey\"),\n      action: \"\"\n    };\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/js/inject-hcaptcha.js",
    "content": "(token) => {\n  document.querySelectorAll('textarea[name=\"h-captcha-response\"]').forEach(el => { el.value = token; });\n  document.querySelectorAll('textarea[name=\"g-recaptcha-response\"]').forEach(el => { el.value = token; });\n\n  let called = false;\n\n  if (!called) {\n    const el = document.querySelector('.h-captcha[data-callback]');\n    if (el) {\n      const name = el.getAttribute('data-callback');\n      if (name && typeof window[name] === 'function') {\n        window[name](token);\n        called = true;\n      }\n    }\n  }\n\n  // callback alone may not trigger navigation\n  const form = document.querySelector('form:has(.h-captcha)') ||\n               document.querySelector('form:has([name=\"h-captcha-response\"])');\n  if (form) form.submit();\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/js/inject-recaptcha.js",
    "content": "(token) => {\n  document.querySelectorAll('[id=\"g-recaptcha-response\"], [name=\"g-recaptcha-response\"]').forEach(el => {\n    el.value = token;\n    el.style.display = 'block';\n  });\n\n  let called = false;\n\n  if (!called) {\n    const el = document.querySelector('.g-recaptcha[data-callback]');\n    if (el) {\n      const name = el.getAttribute('data-callback');\n      if (name && typeof window[name] === 'function') {\n        window[name](token);\n        called = true;\n      }\n    }\n  }\n\n  // ___grecaptcha_cfg.clients holds internal reCAPTCHA state including registered callbacks\n  if (!called && typeof ___grecaptcha_cfg !== 'undefined' && ___grecaptcha_cfg.clients) {\n    try {\n      for (const key in ___grecaptcha_cfg.clients) {\n        const client = ___grecaptcha_cfg.clients[key];\n        const find = (obj, depth) => {\n          if (depth > 4 || !obj || typeof obj !== 'object') return null;\n          if (obj instanceof Node || obj instanceof Window) return null;\n          try {\n            for (const k in obj) {\n              try {\n                const v = obj[k];\n                if (k === 'callback' && typeof v === 'function') return v;\n                if (v && typeof v === 'object' && !(v instanceof Node) && !(v instanceof Window)) {\n                  if (typeof v.callback === 'function') return v.callback;\n                  const found = find(v, depth + 1);\n                  if (found) return found;\n                }\n              } catch(e) { continue; }\n            }\n          } catch(e) { return null; }\n          return null;\n        };\n        const cb = find(client, 0);\n        if (cb) { cb(token); called = true; break; }\n      }\n    } catch(e) {}\n  }\n\n  // callback alone may not trigger navigation\n  const form = document.querySelector('form:has(#g-recaptcha-response)') ||\n               document.querySelector('form:has(.g-recaptcha)');\n  if (form) form.submit();\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/js/inject-turnstile.js",
    "content": "(token) => {\n  document.querySelectorAll('input[name=\"cf-turnstile-response\"]').forEach(el => { el.value = token; });\n\n  let called = false;\n\n  if (!called) {\n    const el = document.querySelector('.cf-turnstile[data-callback]');\n    if (el) {\n      const name = el.getAttribute('data-callback');\n      if (name && typeof window[name] === 'function') {\n        window[name](token);\n        called = true;\n      }\n    }\n  }\n\n  // callback alone may not trigger navigation\n  const form = document.querySelector('form:has(.cf-turnstile)') ||\n               document.querySelector('form:has([name=\"cf-turnstile-response\"])');\n  if (form) form.submit();\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/js/js.go",
    "content": "package js\n\nimport _ \"embed\"\n\nvar (\n\t//go:embed identify.js\n\tIdentifyJS string\n\n\t//go:embed inject-recaptcha.js\n\tInjectRecaptchaJS string\n\n\t//go:embed inject-turnstile.js\n\tInjectTurnstileJS string\n\n\t//go:embed inject-hcaptcha.js\n\tInjectHCaptchaJS string\n)\n"
  },
  {
    "path": "pkg/engine/headless/captcha/solver.go",
    "content": "package captcha\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\ntype Solution struct {\n\tToken    string\n\tProvider Provider\n}\n\ntype Solver interface {\n\tSolve(ctx context.Context, info *Info) (*Solution, error)\n}\n\nfunc NewSolver(provider, apiKey string) (Solver, error) {\n\tconstructor, ok := solverRegistry[provider]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unsupported captcha solver provider: %s\", provider)\n\t}\n\treturn constructor(apiKey)\n}\n\ntype SolverConstructor func(apiKey string) (Solver, error)\n\nvar solverRegistry = map[string]SolverConstructor{}\n\nfunc RegisterSolver(name string, constructor SolverConstructor) {\n\tsolverRegistry[name] = constructor\n}\n"
  },
  {
    "path": "pkg/engine/headless/captcha/solver_test.go",
    "content": "package captcha\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewSolver_UnsupportedProvider(t *testing.T) {\n\t_, err := NewSolver(\"unknown-provider\", \"key\")\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"unsupported\")\n}\n\nfunc TestRegisterSolver(t *testing.T) {\n\tRegisterSolver(\"test-provider\", func(apiKey string) (Solver, error) {\n\t\treturn nil, nil\n\t})\n\tdefer delete(solverRegistry, \"test-provider\")\n\n\t_, ok := solverRegistry[\"test-provider\"]\n\tassert.True(t, ok)\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/crawler.go",
    "content": "package crawler\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/user\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/adrianbrad/queue\"\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/proto\"\n\t\"github.com/go-rod/rod/lib/utils\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/browser\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/captcha\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/diagnostics\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer/simhash\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/graph\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/types\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n)\n\ntype Crawler struct {\n\tlogger        *slog.Logger\n\tlauncher      *browser.Launcher\n\toptions       Options\n\tcrawlQueue    queue.Queue[*types.Action]\n\tcrawlGraph    *graph.CrawlGraph\n\tsimhashOracle *simhash.Oracle\n\tuniqueActions map[string]struct{}\n\tdiagnostics   diagnostics.Writer\n}\n\ntype Options struct {\n\tChromiumPath        string\n\tMaxBrowsers         int\n\tMaxDepth            int\n\tPageMaxTimeout      time.Duration\n\tNoSandbox           bool\n\tShowBrowser         bool\n\tSlowMotion          bool\n\tMaxCrawlDuration    time.Duration\n\tMaxFailureCount     int\n\tTrace               bool\n\tCookieConsentBypass bool\n\tAutomaticFormFill   bool\n\n\t// EnableDiagnostics enables the diagnostics mode\n\t// which writes diagnostic information to a directory\n\t// specified by the DiagnosticsDir optionally.\n\tEnableDiagnostics bool\n\tDiagnosticsDir    string\n\n\tProxy           string\n\tLogger          *slog.Logger\n\tScopeValidator  browser.ScopeValidator\n\tRequestCallback func(*output.Result)\n\tChromeUser      *user.User\n\tCaptchaHandler  *captcha.Handler\n}\n\nvar domNormalizer *normalizer.Normalizer\nvar initOnce sync.Once\nvar initError error\n\nfunc init() {\n\tinitOnce.Do(func() {\n\t\tvar err error\n\t\tdomNormalizer, err = normalizer.New()\n\t\tif err != nil {\n\t\t\tinitError = errors.Wrap(err, \"failed to create domnormalizer\")\n\t\t}\n\t})\n}\n\nfunc New(opts Options) (*Crawler, error) {\n\tif initError != nil {\n\t\treturn nil, initError\n\t}\n\n\tif opts.Logger == nil {\n\t\topts.Logger = slog.Default()\n\t}\n\n\tlauncher, err := browser.NewLauncher(browser.LauncherOptions{\n\t\tChromiumPath:        opts.ChromiumPath,\n\t\tMaxBrowsers:         opts.MaxBrowsers,\n\t\tPageMaxTimeout:      opts.PageMaxTimeout,\n\t\tShowBrowser:         opts.ShowBrowser,\n\t\tRequestCallback:     opts.RequestCallback,\n\t\tSlowMotion:          opts.SlowMotion,\n\t\tScopeValidator:      opts.ScopeValidator,\n\t\tChromeUser:          opts.ChromeUser,\n\t\tTrace:               opts.Trace,\n\t\tCookieConsentBypass: opts.CookieConsentBypass,\n\t\tNoSandbox:           opts.NoSandbox,\n\t\tProxy:               opts.Proxy,\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar diagnosticsWriter diagnostics.Writer\n\tif opts.EnableDiagnostics {\n\t\tdirectory := opts.DiagnosticsDir\n\t\tif directory == \"\" {\n\t\t\tcwd, _ := os.Getwd()\n\t\t\tdirectory = filepath.Join(cwd, fmt.Sprintf(\"katana-diagnostics-%s\", time.Now().Format(time.RFC3339)))\n\t\t}\n\n\t\twriter, err := diagnostics.NewWriter(directory)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdiagnosticsWriter = writer\n\t\topts.DiagnosticsDir = directory\n\t\topts.Logger.Info(\"Diagnostics enabled\", slog.String(\"directory\", directory))\n\t}\n\n\tcrawler := &Crawler{\n\t\tlauncher:      launcher,\n\t\toptions:       opts,\n\t\tlogger:        opts.Logger,\n\t\tuniqueActions: make(map[string]struct{}),\n\t\tdiagnostics:   diagnosticsWriter,\n\t\tsimhashOracle: simhash.NewOracle(),\n\t}\n\treturn crawler, nil\n}\n\nfunc (c *Crawler) Close() {\n\tc.launcher.Close()\n\tif c.diagnostics != nil {\n\t\tif err := c.diagnostics.Close(); err != nil {\n\t\t\tc.logger.Warn(\"Failed to close diagnostics\", slog.String(\"error\", err.Error()))\n\t\t}\n\t}\n}\n\nfunc (c *Crawler) GetCrawlGraph() *graph.CrawlGraph {\n\treturn c.crawlGraph\n}\n\nfunc (c *Crawler) Crawl(URL string) error {\n\tdefer func() {\n\t\tif c.diagnostics == nil {\n\t\t\treturn\n\t\t}\n\t\terr := c.crawlGraph.DrawGraph(filepath.Join(c.options.DiagnosticsDir, \"crawl-graph.dot\"))\n\t\tif err != nil {\n\t\t\tc.logger.Error(\"Failed to draw crawl graph\", slog.String(\"error\", err.Error()))\n\t\t}\n\t}()\n\n\tactions := []*types.Action{{\n\t\tType:     types.ActionTypeLoadURL,\n\t\tInput:    URL,\n\t\tDepth:    0,\n\t\tOriginID: emptyPageHash,\n\t}}\n\n\tcrawlQueue := queue.NewLinked(actions)\n\tc.crawlQueue = crawlQueue\n\n\tcrawlGraph := graph.NewCrawlGraph()\n\tc.crawlGraph = crawlGraph\n\n\t// Add the initial blank state\n\terr := crawlGraph.AddPageState(types.PageState{\n\t\tUniqueID: emptyPageHash,\n\t\tURL:      \"about:blank\",\n\t\tDepth:    0,\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Create a master context that will automatically cancel all page operations\n\t// once the per-URL crawl deadline is reached.\n\tvar (\n\t\tctx    context.Context\n\t\tcancel context.CancelFunc\n\t)\n\tif c.options.MaxCrawlDuration > 0 {\n\t\tctx, cancel = context.WithTimeout(context.Background(), c.options.MaxCrawlDuration)\n\t} else {\n\t\tctx, cancel = context.WithCancel(context.Background())\n\t}\n\tdefer cancel()\n\n\t// Retain the legacy time.After guard as a secondary fail-safe but the\n\t// context cancellation is what actually stops in-flight rod calls.\n\tvar crawlTimeout <-chan time.Time\n\tif c.options.MaxCrawlDuration > 0 {\n\t\tcrawlTimeout = time.After(c.options.MaxCrawlDuration)\n\t}\n\n\tconsecutiveFailures := 0\n\n\tfor {\n\t\tselect {\n\t\tcase <-crawlTimeout:\n\t\t\tc.logger.Debug(\"Max crawl duration reached, stopping crawl\")\n\t\t\treturn nil\n\t\tdefault:\n\t\t\t// Check for too many failures\n\t\t\tif c.options.MaxFailureCount > 0 && consecutiveFailures >= c.options.MaxFailureCount {\n\t\t\t\tc.logger.Warn(\"Too many consecutive failures, stopping crawl\",\n\t\t\t\t\tslog.Int(\"failures\", consecutiveFailures),\n\t\t\t\t\tslog.Int(\"max_allowed\", c.options.MaxFailureCount),\n\t\t\t\t\tslog.Int(\"remaining_actions\", c.crawlQueue.Size()),\n\t\t\t\t)\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\taction, err := crawlQueue.Get()\n\t\t\tif err == queue.ErrNoElementsAvailable {\n\t\t\t\tc.logger.Debug(\"No more actions to process\")\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tif c.options.MaxDepth > 0 && action.Depth > c.options.MaxDepth {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpage, err := c.launcher.GetPageFromPool()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tpage.Page = page.Context(ctx)\n\n\t\t\tc.logger.Debug(\"Processing action\",\n\t\t\t\tslog.String(\"action\", action.String()),\n\t\t\t)\n\n\t\t\tif err := c.crawlFn(ctx, action, page); err != nil {\n\t\t\t\tif err == ErrNoCrawlingAction {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tif errors.Is(err, ErrElementNotVisible) {\n\t\t\t\t\tconsecutiveFailures++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvar npe *rod.NoPointerEventsError\n\t\t\t\tvar ish *rod.InvisibleShapeError\n\t\t\t\tif errors.As(err, &npe) || errors.As(err, &ish) {\n\t\t\t\t\tc.logger.Debug(\"Skipping action as it is not visible\",\n\t\t\t\t\t\tslog.String(\"action\", action.String()),\n\t\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t\t)\n\t\t\t\t\tconsecutiveFailures++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvar ne *rod.NavigationError\n\t\t\t\tif errors.As(err, &ne) {\n\t\t\t\t\tc.logger.Debug(\"Skipping action as navigation failed\",\n\t\t\t\t\t\tslog.String(\"action\", action.String()),\n\t\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t\t)\n\t\t\t\t\tconsecutiveFailures++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif errors.Is(err, ErrNoNavigationPossible) {\n\t\t\t\t\tc.logger.Debug(\"Skipping action as no navigation possible\", slog.String(\"action\", action.String()))\n\t\t\t\t\tconsecutiveFailures++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tvar msce *utils.MaxSleepCountError\n\t\t\t\tif errors.As(err, &msce) {\n\t\t\t\t\tc.logger.Debug(\"Skipping action as it is taking too long\", slog.String(\"action\", action.String()))\n\t\t\t\t\tconsecutiveFailures++\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tc.logger.Debug(\"Skipping action due to site-specific error\",\n\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t\tslog.String(\"action\", action.String()),\n\t\t\t\t)\n\t\t\t\tconsecutiveFailures++\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconsecutiveFailures = 0\n\t\t}\n\t}\n}\n\nvar ErrNoCrawlingAction = errors.New(\"no more actions to crawl\")\n\nfunc (c *Crawler) crawlFn(ctx context.Context, action *types.Action, page *browser.BrowserPage) error {\n\tdefer func() {\n\t\tc.launcher.PutBrowserToPool(page)\n\t}()\n\n\tcurrentPageHash, _, err := getPageHash(page)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.logger.Debug(\"Processing action - current state\",\n\t\tslog.String(\"current_page_hash\", currentPageHash),\n\t\tslog.String(\"action_origin_id\", action.OriginID),\n\t\tslog.String(\"action\", action.String()),\n\t)\n\n\tif action.OriginID != \"\" && action.OriginID != currentPageHash {\n\t\tc.logger.Debug(\"Need to navigate back to origin\",\n\t\t\tslog.String(\"from\", currentPageHash),\n\t\t\tslog.String(\"to\", action.OriginID),\n\t\t)\n\t\tnewPageHash, err := c.navigateBackToStateOrigin(action, page, currentPageHash)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// Refresh the page hash\n\t\tcurrentPageHash = newPageHash\n\t}\n\n\t// FIXME: TODO: Restrict the navigation using scope manager and only\n\t// proceed with actions if the scope is allowed\n\n\t// Check the action and do actions based on action type\n\tif c.diagnostics != nil {\n\t\tif err := c.diagnostics.LogAction(action); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif err := c.executeCrawlStateAction(action, page); err != nil {\n\t\treturn err\n\t}\n\n\t// Check for captcha pages after navigation and attempt to solve them.\n\t// On success, wait for the page to settle and re-enter crawlFn so navigation\n\t// discovery runs on the post-solve page instead of the captcha page.\n\tif c.options.CaptchaHandler != nil {\n\t\thtml, htmlErr := page.HTML()\n\t\tif htmlErr == nil {\n\t\t\thandled, solveErr := c.options.CaptchaHandler.HandleIfCaptcha(ctx, page.Page, html)\n\t\t\tif solveErr != nil {\n\t\t\t\tgologger.Warning().Msgf(\"captcha solving failed: %s\", solveErr)\n\t\t\t}\n\t\t\tif handled && solveErr == nil {\n\t\t\t\t_ = page.WaitPageLoadHeurisitics()\n\t\t\t}\n\t\t\tif handled {\n\t\t\t\t// Skip navigation discovery on captcha pages — the discovered\n\t\t\t\t// links/forms belong to the captcha widget, not the real page.\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n\n\tpageState, err := newPageState(page, action)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif c.diagnostics != nil {\n\t\tif err := c.diagnostics.LogPageState(pageState, diagnostics.PostActionPageState); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpageState.OriginID = currentPageHash\n\n\tif c.options.ScopeValidator != nil {\n\t\tif !c.options.ScopeValidator(pageState.URL) {\n\t\t\tc.logger.Debug(\"Skipping navigation collection - current page is out of scope\",\n\t\t\t\tslog.String(\"url\", pageState.URL),\n\t\t\t)\n\t\t\tif c.crawlQueue.Size() == 0 {\n\t\t\t\treturn ErrNoCrawlingAction\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tnavigations, err := page.FindNavigations()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Log navigations for diagnostics\n\tif c.diagnostics != nil {\n\t\tscreenshotState, err := page.Screenshot(false, &proto.PageCaptureScreenshot{\n\t\t\tFormat: proto.PageCaptureScreenshotFormatPng,\n\t\t})\n\t\tif err != nil {\n\t\t\tc.logger.Error(\"Failed to take screenshot\", slog.String(\"error\", err.Error()))\n\t\t}\n\t\tif err := c.diagnostics.LogPageStateScreenshot(pageState.UniqueID, screenshotState); err != nil {\n\t\t\tc.logger.Error(\"Failed to log page state screenshot\", slog.String(\"error\", err.Error()))\n\t\t}\n\t\tif err := c.diagnostics.LogNavigations(pageState.UniqueID, navigations); err != nil {\n\t\t\tc.logger.Error(\"Failed to log navigations\", slog.String(\"error\", err.Error()))\n\t\t}\n\t}\n\n\tfor _, nav := range navigations {\n\t\tactionHash := nav.Hash()\n\t\tif _, ok := c.uniqueActions[actionHash]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tc.uniqueActions[actionHash] = struct{}{}\n\n\t\t// Check if the element we have is a logout page\n\t\tif nav.Element != nil && isLogoutPage(nav.Element) {\n\t\t\tc.logger.Debug(\"Skipping Found logout page\",\n\t\t\t\tslog.String(\"url\", nav.Element.Attributes[\"href\"]),\n\t\t\t)\n\t\t\tcontinue\n\t\t}\n\t\tnav.OriginID = pageState.UniqueID\n\n\t\tc.logger.Debug(\"Got new navigation\",\n\t\t\tslog.Any(\"navigation\", nav),\n\t\t)\n\t\tif err := c.crawlQueue.Offer(nav); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\terr = c.crawlGraph.AddPageState(*pageState)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// TODO: Check if the page opened new sub pages and if so capture their\n\t// navigation as well as close them so the state change can work.\n\n\tif len(navigations) == 0 && c.crawlQueue.Size() == 0 {\n\t\treturn ErrNoCrawlingAction\n\t}\n\treturn nil\n}\n\nvar ErrElementNotVisible = errors.New(\"element not visible\")\n\nfunc (c *Crawler) executeCrawlStateAction(action *types.Action, page *browser.BrowserPage) error {\n\tvar err error\n\tswitch action.Type {\n\tcase types.ActionTypeLoadURL:\n\t\t// Apply a timeout to every critical Rod call.\n\t\tpTimeout := page.Timeout(c.options.PageMaxTimeout)\n\n\t\tif err := pTimeout.Navigate(action.Input); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = page.WaitPageLoadHeurisitics(); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase types.ActionTypeFillForm:\n\t\tif err := c.processForm(page, action.Form); err != nil {\n\t\t\treturn err\n\t\t}\n\tcase types.ActionTypeLeftClick, types.ActionTypeLeftClickDown:\n\t\tpTimeout := page.Timeout(c.options.PageMaxTimeout)\n\t\telement, err := pTimeout.ElementX(action.Element.XPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\telementTimeout := element.Timeout(c.options.PageMaxTimeout)\n\t\tif err := elementTimeout.ScrollIntoView(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvisible, err := element.Visible()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !visible {\n\t\t\treturn ErrElementNotVisible\n\t\t}\n\n\t\t// Check if element is interactable (not blocked by overlays)\n\t\tinteractable, err := element.Interactable()\n\t\tif err != nil {\n\t\t\tvar ce *rod.CoveredError\n\t\t\tif errors.As(err, &ce) {\n\t\t\t\treturn ErrElementNotVisible\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tif interactable == nil {\n\t\t\treturn ErrElementNotVisible\n\t\t}\n\n\t\tif err := element.Click(proto.InputMouseButtonLeft, 1); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = page.WaitPageLoadHeurisitics(); err != nil {\n\t\t\treturn err\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown action type: %v\", action.Type)\n\t}\n\treturn nil\n}\n\nvar logoutPattern = regexp.MustCompile(`(?i)(log[\\s-]?out|sign[\\s-]?out|signout|deconnexion|cerrar[\\s-]?sesion|sair|abmelden|uitloggen|ausloggen|exit|disconnect|terminate|end[\\s-]?session|salir|desconectar|afmelden|wyloguj|logout|sign[\\s-]?off)`)\n\nfunc isLogoutPage(element *types.HTMLElement) bool {\n\treturn logoutPattern.MatchString(element.TextContent) ||\n\t\tlogoutPattern.MatchString(element.Attributes[\"href\"])\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/diagnostics/diagnostics.go",
    "content": "package diagnostics\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/types\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// Writer is a writer that writes diagnostics to a directory\n// for the katana headless crawler module.\ntype Writer interface {\n\tClose() error\n\tLogAction(action *types.Action) error\n\tLogPageState(state *types.PageState, stateType PageStateType) error\n\tLogNavigations(pageStateID string, navigations []*types.Action) error\n\tLogPageStateScreenshot(pageStateID string, screenshot []byte) error\n}\n\ntype PageStateType string\n\nvar (\n\tPreActionPageState  PageStateType = \"pre-action\"\n\tPostActionPageState PageStateType = \"post-action\"\n)\n\ntype diskWriter struct {\n\tindex     mapsutil.OrderedMap[string, *stateMetadata]\n\tactions   []*types.Action\n\tmu        sync.Mutex\n\tdirectory string\n}\n\ntype stateMetadata struct {\n\tUniqueID  string `json:\"unique_id\"`\n\tURL       string `json:\"url\"`\n\tTitle     string `json:\"title\"`\n\tOccurence int    `json:\"occurence\"`\n\tType      string `json:\"type\"`\n}\n\ntype navigationEntry struct {\n\tPageStateID     string          `json:\"page_state_id\"`\n\tURL             string          `json:\"url\"`\n\tNavigationCount int             `json:\"navigation_count\"`\n\tNavigations     []*types.Action `json:\"navigations\"`\n\tTimestamp       int64           `json:\"timestamp\"`\n}\n\n// NewWriter creates a new Writer.\nfunc NewWriter(directory string) (Writer, error) {\n\tif err := os.MkdirAll(directory, 0755); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &diskWriter{\n\t\tdirectory: directory,\n\t\tindex:     mapsutil.NewOrderedMap[string, *stateMetadata](),\n\t\tactions:   make([]*types.Action, 0),\n\t\tmu:        sync.Mutex{},\n\t}, nil\n}\n\nfunc (w *diskWriter) Close() error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tactionsList := w.actions\n\tmarshallIndented, err := json.MarshalIndent(actionsList, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := os.WriteFile(filepath.Join(w.directory, \"actions.json\"), marshallIndented, 0644); err != nil {\n\t\treturn err\n\t}\n\n\t// Write index to a separate file\n\tvar data []*stateMetadata\n\tw.index.Iterate(func(key string, value *stateMetadata) bool {\n\t\tdata = append(data, value)\n\t\treturn true\n\t})\n\n\tmarshallIndented, err = json.MarshalIndent(data, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn os.WriteFile(filepath.Join(w.directory, \"index.json\"), marshallIndented, 0644)\n}\n\nfunc (w *diskWriter) LogAction(action *types.Action) error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tw.actions = append(w.actions, action)\n\treturn nil\n}\n\nfunc (w *diskWriter) LogPageState(state *types.PageState, stateType PageStateType) error {\n\tw.mu.Lock()\n\tval, ok := w.index.Get(state.UniqueID)\n\tif ok && val != nil {\n\t\tval.Occurence++\n\t\tw.mu.Unlock()\n\t\treturn nil\n\t}\n\tw.index.Set(state.UniqueID, &stateMetadata{\n\t\tURL:       state.URL,\n\t\tTitle:     state.Title,\n\t\tOccurence: 1,\n\t\tType:      string(stateType),\n\t\tUniqueID:  state.UniqueID,\n\t})\n\tw.mu.Unlock()\n\n\t// Write dom to a separate file and remove striped dom\n\t// Create new directory for each state\n\tdom, strippedDOM := state.DOM, state.StrippedDOM\n\n\tdir := filepath.Join(w.directory, state.UniqueID)\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn err\n\t}\n\n\tdomFile := filepath.Join(dir, \"dom.html\")\n\tif err := os.WriteFile(domFile, []byte(dom), 0644); err != nil {\n\t\treturn err\n\t}\n\tstrippedDOMFile := filepath.Join(dir, \"stripped-dom.html\")\n\tif err := os.WriteFile(strippedDOMFile, []byte(strippedDOM), 0644); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (w *diskWriter) LogNavigations(pageStateID string, navigations []*types.Action) error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tmetadata, exists := w.index.Get(pageStateID)\n\turl := \"\"\n\tif exists && metadata != nil {\n\t\turl = metadata.URL\n\t}\n\n\tdir := filepath.Join(w.directory, pageStateID)\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn err\n\t}\n\n\tnavigationsFile := filepath.Join(dir, \"navigations.json\")\n\n\tvar entry navigationEntry\n\tif existingData, err := os.ReadFile(navigationsFile); err == nil {\n\t\tif err := json.Unmarshal(existingData, &entry); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tentry.Navigations = append(entry.Navigations, navigations...)\n\t\tentry.NavigationCount = len(entry.Navigations)\n\t\tentry.Timestamp = time.Now().Unix()\n\t} else {\n\t\tentry = navigationEntry{\n\t\t\tPageStateID:     pageStateID,\n\t\t\tURL:             url,\n\t\t\tNavigationCount: len(navigations),\n\t\t\tNavigations:     navigations,\n\t\t\tTimestamp:       time.Now().Unix(),\n\t\t}\n\t}\n\tmarshalledData, err := json.MarshalIndent(entry, \"\", \"  \")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Write to navigations.json file in the state directory\n\treturn os.WriteFile(navigationsFile, marshalledData, 0644)\n}\n\nfunc (w *diskWriter) LogPageStateScreenshot(pageStateID string, screenshot []byte) error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\n\tdir := filepath.Join(w.directory, pageStateID)\n\tif err := os.MkdirAll(dir, 0755); err != nil {\n\t\treturn err\n\t}\n\tscreenshotFile := filepath.Join(dir, \"screenshot.png\")\n\treturn os.WriteFile(screenshotFile, screenshot, 0644)\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/formfill.go",
    "content": "package crawler\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/proto\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/browser\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/types\"\n\tutilsformfill \"github.com/projectdiscovery/katana/pkg/utils\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\nfunc deriveName(e *types.HTMLElement) string {\n\tif n, ok := e.Attributes[\"name\"]; ok && n != \"\" {\n\t\treturn n\n\t}\n\treturn e.ID\n}\n\nfunc copyAttrs(src map[string]string, skipKeys ...string) mapsutil.OrderedMap[string, string] {\n\tskip := map[string]struct{}{}\n\tfor _, k := range skipKeys {\n\t\tskip[k] = struct{}{}\n\t}\n\tdst := mapsutil.NewOrderedMap[string, string]()\n\tfor k, v := range src {\n\t\tif _, s := skip[k]; !s {\n\t\t\tdst.Set(k, v)\n\t\t}\n\t}\n\treturn dst\n}\n\nfunc convertHTMLElementToFormInput(element *types.HTMLElement) utilsformfill.FormInput {\n\treturn utilsformfill.FormInput{\n\t\tName:       deriveName(element),\n\t\tType:       element.Type,\n\t\tValue:      element.Value,\n\t\tAttributes: copyAttrs(element.Attributes, \"name\", \"value\", \"type\"),\n\t}\n}\n\nfunc convertHTMLElementToFormTextArea(element *types.HTMLElement) utilsformfill.FormTextArea {\n\treturn utilsformfill.FormTextArea{\n\t\tName:       deriveName(element),\n\t\tAttributes: copyAttrs(element.Attributes, \"name\"),\n\t}\n}\n\nfunc convertHTMLElementToFormSelect(element *types.HTMLElement) utilsformfill.FormSelect {\n\treturn utilsformfill.FormSelect{\n\t\tName:          deriveName(element),\n\t\tAttributes:    copyAttrs(element.Attributes, \"name\"),\n\t\tSelectOptions: []utilsformfill.SelectOption{},\n\t}\n}\n\nfunc (c *Crawler) processForm(page *browser.BrowserPage, form *types.HTMLForm) error {\n\tif !c.options.AutomaticFormFill {\n\t\treturn nil\n\t}\n\n\tvar formFields []interface{}\n\tvar submitButton *rod.Element\n\telementMap := make(map[string]*rod.Element)\n\n\tfor _, field := range form.Elements {\n\t\tif field.XPath == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\telement, err := page.ElementX(field.XPath)\n\t\tif err != nil {\n\t\t\tc.logger.Debug(\"Could not find form element\",\n\t\t\t\tslog.String(\"xpath\", field.XPath),\n\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t)\n\t\t\tcontinue\n\t\t}\n\n\t\tfieldName := c.getFieldName(field)\n\n\t\tswitch field.TagName {\n\t\tcase \"INPUT\":\n\t\t\tif field.Type == \"submit\" || field.Type == \"button\" {\n\t\t\t\tif submitButton == nil && field.Type == \"submit\" {\n\t\t\t\t\tsubmitButton = element\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tformInput := convertHTMLElementToFormInput(field)\n\t\t\tformFields = append(formFields, formInput)\n\t\t\tif fieldName != \"\" {\n\t\t\t\telementMap[fieldName] = element\n\t\t\t}\n\n\t\tcase \"TEXTAREA\":\n\t\t\tformTextArea := convertHTMLElementToFormTextArea(field)\n\t\t\tformFields = append(formFields, formTextArea)\n\t\t\tif fieldName != \"\" {\n\t\t\t\telementMap[fieldName] = element\n\t\t\t}\n\n\t\tcase \"SELECT\":\n\t\t\tformSelect := c.buildFormSelectWithOptions(page, field, element)\n\t\t\tformFields = append(formFields, formSelect)\n\t\t\tif fieldName != \"\" {\n\t\t\t\telementMap[fieldName] = element\n\t\t\t}\n\n\t\tcase \"BUTTON\":\n\t\t\tif field.Type == \"submit\" && submitButton == nil {\n\t\t\t\tsubmitButton = element\n\t\t\t}\n\t\t}\n\t}\n\n\tfillSuggestions := utilsformfill.FormFillSuggestions(formFields)\n\n\tif err := c.applyFormSuggestions(fillSuggestions, elementMap); err != nil {\n\t\tc.logger.Debug(\"Error applying form suggestions\", slog.String(\"error\", err.Error()))\n\t}\n\n\tif submitButton != nil {\n\t\tif err := submitButton.Click(proto.InputMouseButtonLeft, 1); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *Crawler) getFieldName(field *types.HTMLElement) string {\n\treturn deriveName(field)\n}\n\nfunc (c *Crawler) buildFormSelectWithOptions(page *browser.BrowserPage, field *types.HTMLElement, element *rod.Element) utilsformfill.FormSelect {\n\tformSelect := convertHTMLElementToFormSelect(field)\n\n\toptions, err := element.Elements(\"option\")\n\tif err == nil && len(options) > 0 {\n\t\tformSelect.SelectOptions = []utilsformfill.SelectOption{}\n\t\tfor _, opt := range options {\n\t\t\toptionValue, _ := opt.Attribute(\"value\")\n\t\t\tif optionValue == nil {\n\t\t\t\ttext, _ := opt.Text()\n\t\t\t\toptionValue = &text\n\t\t\t}\n\n\t\t\tselected, _ := opt.Attribute(\"selected\")\n\t\t\tselectOption := utilsformfill.SelectOption{\n\t\t\t\tValue:    *optionValue,\n\t\t\t\tSelected: \"\",\n\t\t\t}\n\t\t\tif selected != nil {\n\t\t\t\tselectOption.Selected = \"selected\"\n\t\t\t}\n\t\t\tformSelect.SelectOptions = append(formSelect.SelectOptions, selectOption)\n\t\t}\n\t} else {\n\t\tformSelect.SelectOptions = []utilsformfill.SelectOption{\n\t\t\t{Value: utilsformfill.FormData.Placeholder, Selected: \"selected\"},\n\t\t}\n\t}\n\n\treturn formSelect\n}\n\nfunc (c *Crawler) applyFormSuggestions(suggestions mapsutil.OrderedMap[string, string], elementMap map[string]*rod.Element) error {\n\tsuggestions.Iterate(func(fieldName, value string) bool {\n\t\telement, exists := elementMap[fieldName]\n\t\tif !exists || value == \"\" {\n\t\t\treturn true\n\t\t}\n\n\t\ttagName, err := element.Eval(`() => this.tagName`)\n\t\tif err != nil {\n\t\t\tc.logger.Debug(\"Failed to get element tag\",\n\t\t\t\tslog.String(\"field\", fieldName),\n\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t)\n\t\t\treturn true\n\t\t}\n\n\t\tswitch tagName.Value.String() {\n\t\tcase \"INPUT\":\n\t\t\tinputType, _ := element.Attribute(\"type\")\n\t\t\tif inputType != nil {\n\t\t\t\tswitch *inputType {\n\t\t\t\tcase \"checkbox\", \"radio\":\n\t\t\t\t\tif value == \"on\" || value == fieldName {\n\t\t\t\t\t\tif err := element.Click(proto.InputMouseButtonLeft, 1); err != nil {\n\t\t\t\t\t\t\tc.logger.Debug(\"Failed to check input\",\n\t\t\t\t\t\t\t\tslog.String(\"field\", fieldName),\n\t\t\t\t\t\t\t\tslog.String(\"type\", *inputType),\n\t\t\t\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tif err := element.Input(value); err != nil {\n\t\t\t\t\t\tc.logger.Debug(\"Failed to fill input field\",\n\t\t\t\t\t\t\tslog.String(\"field\", fieldName),\n\t\t\t\t\t\t\tslog.String(\"value\", value),\n\t\t\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\tcase \"TEXTAREA\":\n\t\t\tif err := element.Input(value); err != nil {\n\t\t\t\tc.logger.Debug(\"Failed to fill textarea\",\n\t\t\t\t\tslog.String(\"field\", fieldName),\n\t\t\t\t\tslog.String(\"value\", value),\n\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t)\n\t\t\t}\n\n\t\tcase \"SELECT\":\n\t\t\tif err := element.Select([]string{value}, true, rod.SelectorTypeText); err != nil {\n\t\t\t\tvalueSelector := fmt.Sprintf(`[value=\"%s\"]`, value)\n\t\t\t\tif err := element.Select([]string{valueSelector}, true, rod.SelectorTypeCSSSector); err != nil {\n\t\t\t\t\tc.logger.Debug(\"Failed to select option\",\n\t\t\t\t\t\tslog.String(\"field\", fieldName),\n\t\t\t\t\t\tslog.String(\"value\", value),\n\t\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn true\n\t})\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/dom_utils.go",
    "content": "package normalizer\n\nimport (\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"golang.org/x/net/html\"\n)\n\n// DefaultDOMTransformations is default list of CSS selectors to remove from the DOM.\nvar DefaultDOMTransformations = []string{\n\t\"style, script, path\",           // remove script and style tags\n\t\"input[type='hidden']\",          // remove hidden inputs\n\t\"meta[content]\",                 // remove meta tags with content\n\t\"link[rel='stylesheet']\",        // remove stylesheet links\n\t\"svg\",                           // remove svg\n\t\"grammarly-desktop-integration\", // remove grammarly\n\t\"div[class*='ad'], div[id*='ad'], div[class*='banner'], div[id*='banner'], div[class*='pixel'], div[id*='pixel']\", // remove ad, banner and pixel divs\n\t\"input[name*='csrf'], input[name*='token']\", // remove csrf and token inputs\n}\n\n// NoChildrenDomTransformations removes all elements with no children\nvar NoChildrenDomTransformations = []string{\n\t\"div\",    // remove divs with no children\n\t\"span\",   // remove spans with no children\n\t\"form\",   // remove forms with no children\n\t\"iframe\", // remove iframes\n}\n\n// DOMNormalizer is a normalizer for DOM content\ntype DOMNormalizer struct {\n\tcustomTransformations []domTransformationFunc\n}\n\n// NewDOMNormalizer returns a new DOMNormalizer\n//\n// transformations is a list of CSS selectors to remove from the DOM.\nfunc NewDOMNormalizer() *DOMNormalizer {\n\tvar customTransformations []domTransformationFunc\n\tfor _, t := range DefaultDOMTransformations {\n\t\tt := t\n\t\tcustomTransformations = append(customTransformations, func(doc *goquery.Document) {\n\t\t\tdoc.Find(t).Each(func(_ int, s *goquery.Selection) {\n\t\t\t\ts.Remove()\n\t\t\t})\n\t\t})\n\t}\n\n\tfor _, t := range NoChildrenDomTransformations {\n\t\tt := t\n\t\tcustomTransformations = append(customTransformations, func(doc *goquery.Document) {\n\t\t\tdoc.Find(t).Each(func(_ int, s *goquery.Selection) {\n\t\t\t\tif s.Children().Length() == 0 && strings.TrimSpace(s.Text()) == \"\" {\n\t\t\t\t\tif node := s.Get(0); node != nil && len(node.Attr) == 0 {\n\t\t\t\t\t\ts.Remove()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t})\n\t}\n\n\treturn &DOMNormalizer{customTransformations: customTransformations}\n}\n\n// Apply applies the normalizers to the given content\nfunc (d *DOMNormalizer) Apply(content string) (string, error) {\n\tdoc, err := goquery.NewDocumentFromReader(strings.NewReader(content))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// Apply custom transformations first (selector-based removals, etc.)\n\tfor _, f := range d.customTransformations {\n\t\tf(doc)\n\t}\n\t// Apply selection based transformations once at the root (recursive helpers will traverse)\n\tfor _, f := range selectionBasedTransformationFuncs {\n\t\tf(doc.Selection)\n\t}\n\tresult, err := doc.Html()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn result, nil\n}\n\n// domTransformationFunc does required transformation on document.\ntype domTransformationFunc func(doc *goquery.Document)\n\ntype selectionTransformationFunc func(s *goquery.Selection)\n\nvar selectionBasedTransformationFuncs = []selectionTransformationFunc{\n\tremoveCommentsDomTransformationFunc,              // remove comments\n\tremoveClassIDDataAttributesDomTransformationFunc, // remove class, id and data attributes\n}\n\nfunc removeComments(n *html.Node) {\n\tif n.Type == html.CommentNode {\n\t\tn.Parent.RemoveChild(n)\n\t}\n\n\tfor c := n.FirstChild; c != nil; c = c.NextSibling {\n\t\tremoveComments(c)\n\t}\n}\n\nfunc removeCommentsDomTransformationFunc(s *goquery.Selection) {\n\tif n := s.Get(0); n != nil {\n\t\tremoveComments(n)\n\t}\n}\n\nvar attributes = []string{\n\t\"class\",\n\t\"id\",\n\t\"name\",\n\t\"href\",\n\t\"style\",\n\t\"width\",\n\t\"height\",\n\t\"src\",\n\t\"nowrap\",\n\t\"target\",\n\t\"valign\",\n\t\"cellpadding\",\n\t\"cellspacing\",\n\t\"value\",\n\t\"placeholder\",\n\t\"title\",\n\t\"alt\",\n}\n\nfunc removeClassIDDataAttributesDomTransformationFunc(s *goquery.Selection) {\n\tremoveAttributes(s)\n\t// Handle children\n\ts.Children().Each(func(_ int, child *goquery.Selection) {\n\t\tremoveClassIDDataAttributesDomTransformationFunc(child)\n\t})\n}\n\nfunc removeAttributes(s *goquery.Selection) {\n\tfor _, attr := range attributes {\n\t\ts.RemoveAttr(attr)\n\t}\n\n\tfor _, node := range s.Nodes {\n\t\tfor _, attr := range node.Attr {\n\t\t\tattr := attr\n\t\t\tif strings.HasPrefix(attr.Key, \"data-\") || strings.HasPrefix(attr.Key, \"aria-\") || strings.HasPrefix(attr.Key, \"js\") {\n\t\t\t\ts.RemoveAttr(attr.Key)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/dom_utils_test.go",
    "content": "package normalizer\n\nimport (\n\t\"testing\"\n)\n\nfunc TestDOMNormalizer_Apply(t *testing.T) {\n\ttype args struct {\n\t\tcontent string\n\t}\n\tnormalizer := NewDOMNormalizer()\n\n\ttests := []struct {\n\t\tname    string\n\t\td       *DOMNormalizer\n\t\targs    args\n\t\twant    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"comments-style-script\",\n\t\t\td:    normalizer,\n\t\t\targs: args{\n\t\t\t\tcontent: `<html><head><style>body {color: blue;}</style></head><body><h1>Hello World</h1><!-- comment --><script>alert(\"hi\");</script></body></html>`,\n\t\t\t},\n\t\t\twant:    \"<html><head></head><body><h1>Hello World</h1></body></html>\",\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"hidden input\",\n\t\t\td:    normalizer,\n\t\t\targs: args{\n\t\t\t\tcontent: `<html><head></head><body><input type=\"hidden\" name=\"test\" value=\"test\"></body></html>`,\n\t\t\t},\n\t\t\twant:    \"<html><head></head><body></body></html>\",\n\t\t\twantErr: false,\n\t\t},\n\t\t// write tests for other cases\n\t\t{\n\t\t\tname: \"csrf\",\n\t\t\td:    normalizer,\n\t\t\targs: args{\n\t\t\t\tcontent: `<html><head></head><body><input name=\"csrf\" value=\"test\"></body></html>`,\n\t\t\t},\n\t\t\twant:    \"<html><head></head><body></body></html>\",\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"class-id-data-attributes\",\n\t\t\td:    normalizer,\n\t\t\targs: args{\n\t\t\t\tcontent: `<html><head></head><body><div class=\"test\" id=\"test\" data-test=\"test\"></div></body></html>`,\n\t\t\t},\n\t\t\twant: \"<html><head></head><body><div></div></body></html>\",\n\t\t},\n\t\t{\n\t\t\tname: \"inline-style\",\n\t\t\td:    normalizer,\n\t\t\targs: args{\n\t\t\t\tcontent: `<html><head></head><body><div style=\"color: blue;\"></div></body></html>`,\n\t\t\t},\n\t\t\twant: \"<html><head></head><body><div></div></body></html>\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\td := normalizer\n\t\t\tgot, err := d.Apply(tt.args.content)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"DOMNormalizer.Apply() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"DOMNormalizer.Apply() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/helpers.go",
    "content": "package normalizer\n\n// dateTimePatterns contains regex patterns for various date and time formats\n// The ordering is important for proper matching\nvar dateTimePatterns = []string{\n\t/* with days */\n\t\"[a-zA-Z]{3,} [0-9]{1,2} [a-zA-Z]{3,} [0-9]{4}\",\n\t\"[a-zA-Z]{3,} [0-9]{1,2} [a-zA-Z]{3,} '[0-9]{2}\",\n\t\"[a-zA-Z]{3,} [0-9]{1,2} [a-zA-Z]{3,}\",\n\n\t/* only numeric */\n\t\"[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}\",\n\t\"[0-9]{4}\\\\.[0-9]{1,2}\\\\.[0-9]{1,2}\",\n\t\"[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}\",\n\t\"[0-9]{1,2}-[0-9]{1,2}-[0-9]{4}\",\n\t\"[0-9]{1,2}\\\\.[0-9]{1,2}\\\\.[0-9]{4}\",\n\t\"[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}\",\n\t\"[0-9]{1,2}-[0-9]{1,2}-'[0-9]{2}\",\n\t\"[0-9]{1,2}\\\\.[0-9]{1,2}\\\\.'[0-9]{2}\",\n\t\"[0-9]{1,2}/[0-9]{1,2}/'[0-9]{2}\",\n\t\"[0-9]{1,2}-[0-9]{1,2}-[0-9]{2}\",\n\t\"[0-9]{1,2}\\\\.[0-9]{1,2}\\\\.[0-9]{2}\",\n\t\"[0-9]{1,2}/[0-9]{1,2}/[0-9]{2}\",\n\n\t/* long months */\n\t\"[0-9]{1,2} [a-zA-Z]{3,} [0-9]{4}\",\n\t\"[0-9]{1,2}th [a-zA-Z]{3,} [0-9]{4}\",\n\t\"[0-9]{1,2}th [a-zA-Z]{3,}\",\n\t\"[0-9]{4} [a-zA-Z]{3,} [0-9]{1,2}\",\n\t\"[0-9]{4}[a-zA-Z]{3,}[0-9]{1,2}\",\n\t\"[a-zA-Z]{3,} [0-9]{4}\",\n\t\"[a-zA-Z]{3,} '[0-9]{2}\",\n\t\"[a-zA-Z]{3,} [0-9]{1,2} [0-9]{4}\",\n\t\"[a-zA-Z]{3,} [0-9]{1,2}, [0-9]{4}\",\n\t\"[a-zA-Z]{3,} [0-9]{1,2} '[0-9]{2}\",\n\t\"[a-zA-Z]{3,} [0-9]{1,2}, '[0-9]{2}\",\n\n\t/* Times */\n\t\"[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}( )?(pm|PM|am|AM)\",\n\t\"[0-9]{1,2}:[0-9]{1,2}( )?(pm|PM|am|AM)\",\n\t\"[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}\",\n\t\"[0-9]{1,2}:[0-9]{1,2}\",\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/normalizer.go",
    "content": "package normalizer\n\nimport (\n\t\"fmt\"\n\t\"html\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/pkg/errors\"\n\thtmlpkg \"golang.org/x/net/html\"\n)\n\nvar whiteSpacesRegex = regexp.MustCompile(`[\\r\\n]+|\\s+`)\n\ntype Normalizer struct {\n\tdom  *DOMNormalizer\n\ttext *TextNormalizer\n}\n\n// New returns a new Normalizer\nfunc New() (*Normalizer, error) {\n\ttextNormalizer, err := NewTextNormalizer()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"failed to create text normalizer\")\n\t}\n\tdomNormalizer := NewDOMNormalizer()\n\treturn &Normalizer{\n\t\tdom:  domNormalizer,\n\t\ttext: textNormalizer,\n\t}, nil\n}\n\n// Apply applies the normalizers to the given content\n//\n// It normalizes the given content by:\n// - Applying the DOM normalizer\n// - Applying the text normalizer\n// - Denormalizing it\nfunc (n *Normalizer) Apply(text string) (string, error) {\n\tfirst := normalizeDocument(text)\n\n\tfirstpass, err := n.dom.Apply(first)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to apply DOM normalizer\")\n\t}\n\n\tsecondpass, err := stripTextContent(firstpass)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"failed to strip text content\")\n\t}\n\n\tthirdpass := n.text.Apply(secondpass)\n\n\tfourthpass := normalizeDocument(thirdpass)\n\treturn fourthpass, nil\n}\n\n// normalizeDocument normalizes the given document by:\n// - Lowercasing it\n// - URL decoding it\n// - HTML entity decoding it\n// - Replacing all whitespace variations with a space\n// - Trimming the document whitespaces\nfunc normalizeDocument(text string) string {\n\t// Lowercase the document\n\tlowercased := strings.ToLower(text)\n\n\t// Convert hexadecimal escape sequences to HTML entities\n\tconverted := convertHexEscapeSequencesToEntities(lowercased)\n\tunescaped := html.UnescapeString(converted)\n\n\t// URL Decode and HTML entity decode the document to standardize it.\n\turlDecoded, err := url.QueryUnescape(unescaped)\n\tif err != nil {\n\t\turlDecoded = unescaped\n\t}\n\n\t// Replace all whitespaces with a space\n\tnormalized := whiteSpacesRegex.ReplaceAllString(urlDecoded, \" \")\n\n\t// Trim the document to remove leading and trailing whitespaces\n\treturn strings.Trim(normalized, \" \\r\\n\\t\")\n}\n\nfunc replaceHexEscapeSequence(match string) string {\n\t// Remove the '\\x' prefix\n\tcode := strings.TrimPrefix(match, \"\\\\x\")\n\t// Parse the hexadecimal code to an integer\n\tvalue, err := strconv.ParseInt(code, 16, 32)\n\tif err != nil {\n\t\t// If there's an error, return the original match\n\t\treturn match\n\t}\n\t// Return the corresponding HTML entity\n\treturn fmt.Sprintf(\"&#x%x;\", value)\n}\n\n// Define the regex pattern to match hexadecimal escape sequences\nvar pattern = regexp.MustCompile(`\\\\x[0-9a-fA-F]{2}`)\n\nfunc convertHexEscapeSequencesToEntities(input string) string {\n\treturn pattern.ReplaceAllStringFunc(input, func(match string) string {\n\t\treturn replaceHexEscapeSequence(match)\n\t})\n}\n\nfunc stripTextContent(content string) (string, error) {\n\tdoc, err := goquery.NewDocumentFromReader(strings.NewReader(content))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tdoc.Find(\"h1, h2, h3, h4, h5, h6, p, span, div, td, th, li, a\").Each(func(_ int, s *goquery.Selection) {\n\t\tremoveTextNodesFromSelection(s)\n\t})\n\n\tresult, err := doc.Html()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn result, nil\n}\n\nfunc removeTextNodesFromSelection(s *goquery.Selection) {\n\tnode := s.Get(0)\n\tif node == nil {\n\t\treturn\n\t}\n\tfor c := node.FirstChild; c != nil; {\n\t\tnext := c.NextSibling\n\t\tif c.Type == htmlpkg.TextNode {\n\t\t\tnode.RemoveChild(c)\n\t\t}\n\t\tc = next\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/simhash/simhash.go",
    "content": "// Package simhash implements SimHash algorithm for near-duplicate detection.\n//\n// The original algorithm is taken from: https://github.com/yahoo/gryffin/blob/master/html-distance/feature.go\n// Optimized implementation with performance improvements.\n//\n// Original Copyright 2015, Yahoo Inc. All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\npackage simhash\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"sync\"\n\n\t\"github.com/mfonda/simhash\"\n\t\"golang.org/x/net/html\"\n)\n\n// Constants for optimization\nconst (\n\tmaxTokens       = 5000\n\tfeaturesBufSize = 1000\n)\n\n// Pre-allocated buffers for feature generation\nvar bufferPool = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn &bytes.Buffer{}\n\t},\n}\n\nfunc fingerprintOptimized(r io.Reader, shingle int) uint64 {\n\tif shingle < 1 {\n\t\tshingle = 1\n\t}\n\n\tv := simhash.Vector{}\n\tz := html.NewTokenizer(r)\n\n\tfeatures := make([]string, 0, featuresBufSize)\n\twindow := make([][]byte, shingle)\n\twindowIndex := 0\n\n\t// Single-pass tokenization and feature extraction\n\tcount := 0\n\tfor count < maxTokens {\n\t\tif tt := z.Next(); tt == html.ErrorToken {\n\t\t\tbreak\n\t\t}\n\t\tt := z.Token()\n\t\tcount++\n\n\t\textractFeatures(&t, &features)\n\t}\n\n\t// Process features with shingling\n\tbuf := bufferPool.Get().(*bytes.Buffer)\n\tdefer func() {\n\t\tbuf.Reset()\n\t\tbufferPool.Put(buf)\n\t}()\n\n\tfor _, f := range features {\n\t\twindow[windowIndex%shingle] = []byte(f)\n\t\twindowIndex++\n\n\t\tbuf.Reset()\n\t\tfor i := 0; i < shingle; i++ {\n\t\t\tif i > 0 {\n\t\t\t\tbuf.WriteByte(' ')\n\t\t\t}\n\t\t\tbuf.Write(window[(windowIndex-shingle+i+shingle)%shingle])\n\t\t}\n\n\t\tsum := simhash.NewFeature(buf.Bytes()).Sum()\n\n\t\tfor i := uint8(0); i < 64; i++ {\n\t\t\tif (sum>>i)&1 == 1 {\n\t\t\t\tv[i]++\n\t\t\t} else {\n\t\t\t\tv[i]--\n\t\t\t}\n\t\t}\n\t}\n\n\treturn simhash.Fingerprint(v)\n}\n\n// extractFeatures extracts features from HTML token and appends to slice\nfunc extractFeatures(t *html.Token, features *[]string) {\n\t// Pre-allocate string builder for efficiency\n\tvar s string\n\n\tswitch t.Type {\n\tcase html.StartTagToken:\n\t\ts = \"A:\" + t.DataAtom.String()\n\tcase html.EndTagToken:\n\t\ts = \"B:\" + t.DataAtom.String()\n\tcase html.SelfClosingTagToken:\n\t\ts = \"C:\" + t.DataAtom.String()\n\tcase html.DoctypeToken:\n\t\ts = \"D:\" + string(t.Data)\n\tcase html.CommentToken:\n\t\ts = \"E:\" + string(t.Data)\n\tcase html.TextToken:\n\t\ts = \"F:\" + string(t.Data)\n\tcase html.ErrorToken:\n\t\ts = \"Z:\" + string(t.Data)\n\tdefault:\n\t\treturn\n\t}\n\n\t*features = append(*features, s)\n\n\t// Process attributes\n\tfor _, attr := range t.Attr {\n\t\tswitch attr.Key {\n\t\tcase \"class\", \"name\", \"rel\":\n\t\t\ts = fmt.Sprintf(\"G:%s:%s:%s\", t.DataAtom.String(), attr.Key, attr.Val)\n\t\tdefault:\n\t\t\ts = fmt.Sprintf(\"G:%s:%s\", t.DataAtom.String(), attr.Key)\n\t\t}\n\t\t*features = append(*features, s)\n\t}\n}\n\n// Fingerprint is the original function signature for compatibility\nfunc Fingerprint(r io.Reader, shingle int) uint64 {\n\treturn fingerprintOptimized(r, shingle)\n}\n\ntype Oracle struct {\n\tfingerprint uint64      // node value.\n\tnodes       [65]*Oracle // leaf nodes\n}\n\n// NewOracle return an oracle that could tell if the fingerprint has been seen or not.\nfunc NewOracle() *Oracle {\n\treturn newNode(0)\n}\n\nfunc newNode(f uint64) *Oracle {\n\treturn &Oracle{fingerprint: f}\n}\n\n// Distance return the similarity distance between two fingerprint.\nfunc Distance(a, b uint64) uint8 {\n\treturn simhash.Compare(a, b)\n}\n\n// See asks the oracle to see the fingerprint.\nfunc (n *Oracle) See(f uint64) *Oracle {\n\td := Distance(n.fingerprint, f)\n\n\tif d == 0 {\n\t\t// current node with same fingerprint.\n\t\treturn n\n\t}\n\n\t// the target node is already set,\n\tif c := n.nodes[d]; c != nil {\n\t\treturn c.See(f)\n\t}\n\n\tn.nodes[d] = newNode(f)\n\treturn n.nodes[d]\n}\n\n// Seen asks the oracle if anything closed to the fingerprint in a range (r) is seen before.\nfunc (n *Oracle) Seen(f uint64, r uint8) bool {\n\td := Distance(n.fingerprint, f)\n\tif d <= r {\n\t\treturn true\n\t}\n\n\t// Check the direct child at distance d first\n\tif c := n.nodes[d]; c != nil && c.Seen(f, r) {\n\t\treturn true\n\t}\n\n\t// Optimized search: start from closest distance and expand outward\n\tfor offset := uint8(1); offset <= r; offset++ {\n\t\t// Check both directions\n\t\tif d >= offset {\n\t\t\tif c := n.nodes[d-offset]; c != nil && c.Seen(f, r) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\tif d+offset <= 64 {\n\t\t\tif c := n.nodes[d+offset]; c != nil && c.Seen(f, r) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/simhash/simhash_test.go",
    "content": "package simhash\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nvar (\n\thtmlA = `<html><body><p>Hello World</p></body></html>`\n\t// htmlB differs by an exclamation mark. This should keep the documents fairly similar\n\t// while still resulting in a different SimHash fingerprint.\n\thtmlB = `<html><body><p>Hello World!</p></body></html>`\n)\n\n// TestFingerprintDeterministic ensures that hashing the same document twice produces\n// identical fingerprints and that a shingle value of 0 gracefully falls back to 1.\nfunc TestFingerprintDeterministic(t *testing.T) {\n\tin1 := strings.NewReader(htmlA)\n\tin2 := strings.NewReader(htmlA)\n\n\tfp1 := Fingerprint(in1, 0)\n\tfp2 := Fingerprint(in2, 1)\n\n\tif fp1 != fp2 {\n\t\tt.Fatalf(\"expected identical fingerprints, got %d and %d\", fp1, fp2)\n\t}\n}\n\n// TestFingerprintSimilarity checks that two similar documents yield fingerprints with\n// a small (non-zero) Hamming distance.\nfunc TestFingerprintSimilarity(t *testing.T) {\n\tfpA := Fingerprint(strings.NewReader(htmlA), 3)\n\tfpB := Fingerprint(strings.NewReader(htmlB), 3)\n\n\td := Distance(fpA, fpB)\n\tif d == 0 {\n\t\tt.Fatalf(\"expected different fingerprints, got distance 0\")\n\t}\n\n\tconst maxReasonableDistance = 20 // out of a maximum of 64\n\tif d > maxReasonableDistance {\n\t\tt.Fatalf(\"expected similar documents to have distance <= %d, got %d\", maxReasonableDistance, d)\n\t}\n}\n\n// TestOracle validates the basic behaviour of the Oracle structure.\nfunc TestOracle(t *testing.T) {\n\tfpA := Fingerprint(strings.NewReader(htmlA), 3)\n\tfpB := Fingerprint(strings.NewReader(htmlB), 3)\n\n\to := NewOracle()\n\n\tif o.Seen(fpA, 0) {\n\t\tt.Fatalf(\"oracle should not have seen fingerprint yet\")\n\t}\n\n\t// Teach the oracle about fpA.\n\to.See(fpA)\n\n\tif !o.Seen(fpA, 0) {\n\t\tt.Fatalf(\"oracle should recognise an identical fingerprint once seen\")\n\t}\n\n\tif o.Seen(fpB, 0) {\n\t\tt.Fatalf(\"oracle should not treat different fingerprint as identical when r=0\")\n\t}\n\n\tr := Distance(fpA, fpB)\n\tif !o.Seen(fpB, r) {\n\t\tt.Fatalf(\"oracle should recognise fingerprint within distance %d\", r)\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/text_utils.go",
    "content": "package normalizer\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"slices\"\n)\n\n// DefaultTextPatterns is a list of regex patterns for the text normalizer\nvar DefaultTextPatterns = []string{\n\t// emailAddress\n\t`\\b(?i)[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b`,\n\t// ipAddress\n\t`\\b(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)){3}\\b`,\n\t// uuid\n\t`\\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\\b`,\n\t// relativeDates\n\t`\\b(?:[0-9]{1,2}\\s(?:days?|weeks?|months?|years?)\\s(?:ago|from\\s+now))\\b`,\n\t// priceAmounts (no leading \\b due to currency symbols)\n\t`[\\$€£¥]\\s*\\d+(?:\\.\\d{1,2})?\\b`,\n\t// phoneNumbers\n\t`\\b\\+?\\d{7,15}\\b`,\n\t// ssnNumbers\n\t`\\b\\d{3}-\\d{2}-\\d{4}\\b`,\n\t// timestampRegex\n\t`\\b(?:(?:[0-9]{4}-[0-9]{2}-[0-9]{2})|(?:(?:[0-9]{2}\\/){2}[0-9]{4}))\\s(?:[0-9]{2}:[0-9]{2}:[0-9]{2})\\b`,\n}\n\n// TextNormalizer is a normalizer for text\ntype TextNormalizer struct {\n\t// patterns is a list of regex patterns for the text normalizer\n\tpatterns []*regexp.Regexp\n}\n\n// NewTextNormalizer returns a new TextNormalizer\n//\n// patterns is a list of regex patterns for the text normalizer\n// DefaultTextPatterns is used if patterns is nil. See DefaultTextPatterns for more info.\nfunc NewTextNormalizer() (*TextNormalizer, error) {\n\tpatterns := slices.Clone(DefaultTextPatterns)\n\tpatterns = append(patterns, dateTimePatterns...)\n\n\tvar compiledPatterns []*regexp.Regexp\n\tfor _, pattern := range patterns {\n\t\tpattern := pattern\n\t\tcompiledPattern, err := regexp.Compile(pattern)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error compiling pattern %s: %v\", pattern, err)\n\t\t}\n\t\tcompiledPatterns = append(compiledPatterns, compiledPattern)\n\t}\n\treturn &TextNormalizer{patterns: compiledPatterns}, nil\n}\n\n// Apply applies the patterns to the text and returns the normalized text\nfunc (n *TextNormalizer) Apply(text string) string {\n\tfor _, pattern := range n.patterns {\n\t\tpattern := pattern\n\t\ttext = pattern.ReplaceAllString(text, \"\")\n\t}\n\treturn text\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/normalizer/text_utils_test.go",
    "content": "package normalizer\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestTextNormalizer_AllPatterns(t *testing.T) {\n\tnormalizer, err := NewTextNormalizer()\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create normalizer: %v\", err)\n\t}\n\n\ttestText := `\n\t\tContact us at test@example.com or admin@SITE.ORG for support.\n\t\tServer IP: 192.168.1.1 and public IP: 8.8.8.8\n\t\tInvalid IPs should not match: 999.999.999.999 or 300.400.500.600\n\t\tUUID: 550e8400-e29b-41d4-a716-446655440000\n\t\tRelative dates: 5 days ago, 2 weeks from now, 10 months ago\n\t\tPrices: $19.99, €50.00, £25.50, ¥1000\n\t\tPhone numbers: +1234567890, +447911123456, +33123456789\n\t\tSSN: 123-45-6789, 987-65-4321\n\t\tTimestamps: 2023-12-25 14:30:00, 12/25/2023 09:15:30\n\t`\n\n\tresult := normalizer.Apply(testText)\n\n\t// Check that sensitive data was removed\n\ttestCases := []struct {\n\t\tpattern         string\n\t\tshouldBeRemoved bool\n\t\tdescription     string\n\t}{\n\t\t{\"test@example.com\", true, \"lowercase email\"},\n\t\t{\"admin@SITE.ORG\", true, \"uppercase email\"},\n\t\t{\"192.168.1.1\", true, \"private IP\"},\n\t\t{\"8.8.8.8\", true, \"public IP\"},\n\t\t{\"999.999.999.999\", false, \"invalid IP (too high octets)\"},\n\t\t{\"300.400.500.600\", false, \"invalid IP (too high octets)\"},\n\t\t{\"550e8400-e29b-41d4-a716-446655440000\", true, \"UUID\"},\n\t\t{\"5 days ago\", true, \"relative date - days ago\"},\n\t\t{\"2 weeks from now\", true, \"relative date - weeks from now\"},\n\t\t{\"10 months ago\", true, \"relative date - months ago\"},\n\t\t{\"$19.99\", true, \"USD price\"},\n\t\t{\"€50.00\", true, \"EUR price\"},\n\t\t{\"£25.50\", true, \"GBP price\"},\n\t\t{\"¥1000\", true, \"JPY price\"},\n\t\t{\"+1234567890\", true, \"international phone\"},\n\t\t{\"+447911123456\", true, \"UK phone\"},\n\t\t{\"+33123456789\", true, \"French phone\"},\n\t\t{\"123-45-6789\", true, \"SSN format 1\"},\n\t\t{\"987-65-4321\", true, \"SSN format 2\"},\n\t\t{\"2023-12-25 14:30:00\", true, \"ISO timestamp\"},\n\t\t{\"12/25/2023 09:15:30\", true, \"US timestamp\"},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tif tc.shouldBeRemoved {\n\t\t\tif strings.Contains(result, tc.pattern) {\n\t\t\t\tt.Errorf(\"%s: Pattern '%s' should have been removed but was found in result\", tc.description, tc.pattern)\n\t\t\t}\n\t\t} else {\n\t\t\tif !strings.Contains(result, tc.pattern) {\n\t\t\t\tt.Errorf(\"%s: Invalid pattern '%s' should not have been removed but was not found in result\", tc.description, tc.pattern)\n\t\t\t}\n\t\t}\n\t}\n\n\tt.Logf(\"Original text length: %d\", len(testText))\n\tt.Logf(\"Normalized text length: %d\", len(result))\n\tt.Logf(\"Normalized result: %s\", result)\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/state.go",
    "content": "package crawler\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strings\"\n\n\tgraphlib \"github.com/dominikbraun/graph\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/browser\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/diagnostics\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer/simhash\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/types\"\n)\n\nvar emptyPageHash = sha256Hash(\"\")\n\nconst simhashThreshold = 2 // Allow up to 2 bits difference\n\nfunc (c *Crawler) isCorrectNavigation(page *browser.BrowserPage, action *types.Action) (string, *types.PageState, error) {\n\tcurrentPageHash, pageState, err := getPageHash(page)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tif currentPageHash == action.OriginID {\n\t\treturn currentPageHash, pageState, nil\n\t}\n\n\t// Get the origin page state to compare SimHash\n\toriginPageState, err := c.crawlGraph.GetPageState(action.OriginID)\n\tif err != nil {\n\t\treturn \"\", pageState, fmt.Errorf(\"failed to get origin page state: %w\", err)\n\t}\n\n\tif pageState != nil && originPageState != nil {\n\t\tdistance := simhash.Distance(pageState.SimHash, originPageState.SimHash)\n\t\tif distance <= simhashThreshold {\n\t\t\tc.logger.Debug(\"Page is similar enough to origin, proceeding\",\n\t\t\t\tslog.String(\"current_hash\", currentPageHash),\n\t\t\t\tslog.String(\"origin_hash\", action.OriginID),\n\t\t\t\tslog.Uint64(\"simhash_distance\", uint64(distance)),\n\t\t\t)\n\t\t\t// Treat this page as the origin state to avoid creating a new vertex\n\t\t\treturn originPageState.UniqueID, pageState, nil\n\t\t}\n\t}\n\n\treturn \"\", pageState, fmt.Errorf(\"failed to navigate back to origin page: %s != %s\", currentPageHash, action.OriginID)\n}\n\nfunc getPageHash(page *browser.BrowserPage) (string, *types.PageState, error) {\n\tpageState, err := newPageState(page, nil)\n\tif err == ErrEmptyPage {\n\t\treturn emptyPageHash, nil, nil\n\t}\n\tif err != nil {\n\t\treturn \"\", nil, errors.Wrap(err, \"could not get page state\")\n\t}\n\treturn pageState.UniqueID, pageState, nil\n}\n\nvar ErrEmptyPage = errors.New(\"page is empty\")\n\nfunc newPageState(page *browser.BrowserPage, action *types.Action) (*types.PageState, error) {\n\tpageInfo, err := page.Info()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get page info\")\n\t}\n\tif pageInfo.URL == \"\" || pageInfo.URL == \"about:blank\" {\n\t\treturn nil, ErrEmptyPage\n\t}\n\n\touterHTML, err := page.HTML()\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get html content\")\n\t}\n\n\tstate := &types.PageState{\n\t\tURL:              pageInfo.URL,\n\t\tDOM:              outerHTML,\n\t\tNavigationAction: action,\n\t\tTitle:            pageInfo.Title,\n\t}\n\tif action != nil {\n\t\tstate.Depth = action.Depth + 1\n\t}\n\tstrippedDOM, err := getStrippedDOM(outerHTML)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get stripped dom\")\n\t}\n\tstate.StrippedDOM = strippedDOM\n\n\t// Get sha256 hash of the stripped dom\n\tstate.UniqueID = sha256Hash(strippedDOM)\n\tstate.SimHash = simhash.Fingerprint(strings.NewReader(strippedDOM), 3)\n\n\treturn state, nil\n}\n\nfunc sha256Hash(item string) string {\n\thasher := sha256.New()\n\thasher.Write([]byte(item))\n\thashItem := hex.EncodeToString(hasher.Sum(nil))\n\treturn hashItem\n}\n\nfunc getStrippedDOM(contents string) (string, error) {\n\tnormalized, err := domNormalizer.Apply(contents)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"could not normalize dom\")\n\t}\n\treturn normalized, nil\n}\n\nvar ErrNoNavigationPossible = errors.New(\"no navigation possible\")\n\n// navigateBackToStateOrigin implements the logic to navigate back to the state origin\n//\n// It implements different logics as an optimization to decide\n// how to navigate back.\n//\n//  1. If the action has an element, check if the element is visible on the current page\n//     If the element is visible, directly use that to navigate.\n//\n//  2. If we have browser history, and the page is in the history which was the origin\n//     of the action, then we can directly use the browser history to navigate back.\n//\n// 3. If all else fails, we have the shortest path navigation.\nfunc (c *Crawler) navigateBackToStateOrigin(action *types.Action, page *browser.BrowserPage, currentPageHash string) (string, error) {\n\tc.logger.Debug(\"Found action with different origin id\",\n\t\tslog.String(\"action_origin_id\", action.OriginID),\n\t\tslog.String(\"current_page_hash\", currentPageHash),\n\t)\n\n\t// Get vertex from the graph\n\toriginPageState, err := c.crawlGraph.GetPageState(action.OriginID)\n\tif err != nil {\n\t\tc.logger.Debug(\"Failed to get origin page state\", slog.String(\"error\", err.Error()))\n\t\treturn \"\", err\n\t}\n\n\t// First, check if the element we want to interact with exists on current page\n\tif action.Element != nil && currentPageHash != emptyPageHash {\n\t\tnewPageHash, err := c.tryElementNavigation(page, action, currentPageHash)\n\t\tif err != nil {\n\t\t\tc.logger.Debug(\"Failed to navigate back to origin page using element\", slog.String(\"error\", err.Error()))\n\t\t}\n\t\tif newPageHash != \"\" {\n\t\t\treturn newPageHash, nil\n\t\t}\n\t}\n\n\t// Try to see if we can move back using the browser history\n\tnewPageHash, err := c.tryBrowserHistoryNavigation(page, originPageState, action)\n\tif err != nil {\n\t\tc.logger.Debug(\"Failed to navigate back using browser history\", slog.String(\"error\", err.Error()))\n\t}\n\tif newPageHash != \"\" {\n\t\treturn newPageHash, nil\n\t}\n\n\t// Finally try Shortest path walking from root.\n\tnewPageHash, err = c.tryShortestPathNavigation(action, page, currentPageHash)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif newPageHash == \"\" {\n\t\treturn \"\", ErrNoNavigationPossible\n\t}\n\treturn newPageHash, nil\n}\n\nfunc (c *Crawler) tryElementNavigation(page *browser.BrowserPage, action *types.Action, currentPageHash string) (string, error) {\n\telement, err := page.ElementX(action.Element.XPath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvisible, err := element.Visible()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !visible {\n\t\treturn \"\", nil\n\t}\n\n\t// Also ensure its interactable\n\tinteractable, err := element.Interactable()\n\tif err != nil || interactable == nil {\n\t\treturn \"\", nil\n\t}\n\n\t// Ensure its the same element\n\thtmlElement, err := page.GetElementFromXpath(action.Element.XPath)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t// Ensure its the same element with stronger identity matching\n\tif isElementMatch(htmlElement, action.Element) {\n\t\tc.logger.Debug(\"Found target element on current page, proceeding without navigation\")\n\t\t// FIXME: Return the origin element ID so that the graph shows\n\t\t// correctly the fastest way to reach the state.\n\t\treturn action.OriginID, nil\n\t}\n\treturn \"\", nil\n}\n\n// isElementMatch implements stronger identity matching logic to reduce false positives.\n// It treats identical ID as definitive match, otherwise requires both Classes and TextContent\n// to match, or enforces at least two matching non-empty attributes.\nfunc isElementMatch(current, target *types.HTMLElement) bool {\n\tif current == nil || target == nil {\n\t\treturn false\n\t}\n\t// Definitive match: identical non-empty IDs\n\tif current.ID != \"\" && target.ID != \"\" && current.ID == target.ID {\n\t\treturn true\n\t}\n\tmatchCount := 0\n\n\tif current.Classes != \"\" && target.Classes != \"\" && current.Classes == target.Classes {\n\t\tmatchCount++\n\t}\n\tif current.TextContent != \"\" && target.TextContent != \"\" && current.TextContent == target.TextContent {\n\t\tmatchCount++\n\t}\n\tif current.TagName != \"\" && target.TagName != \"\" && current.TagName == target.TagName {\n\t\tmatchCount++\n\t}\n\t// Require at least two matching non-empty attributes for a positive match\n\t// This ensures stronger identity verification while still allowing reasonable fallbacks\n\treturn matchCount >= 2\n}\n\nfunc (c *Crawler) tryBrowserHistoryNavigation(page *browser.BrowserPage, originPageState *types.PageState, action *types.Action) (string, error) {\n\tcanNavigateBack, stepsBack, err := c.isBackNavigationPossible(page, originPageState)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !canNavigateBack {\n\t\treturn \"\", nil\n\t}\n\n\tc.logger.Debug(\"Navigating back using browser history\", slog.Int(\"steps_back\", stepsBack))\n\n\tvar navigatedSuccessfully bool\n\tfor i := 0; i < stepsBack; i++ {\n\t\tif err := page.NavigateBack(); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tnavigatedSuccessfully = true\n\t}\n\n\tif !navigatedSuccessfully {\n\t\treturn \"\", nil\n\t}\n\n\tif err := page.WaitPageLoadHeurisitics(); err != nil {\n\t\tc.logger.Debug(\"Failed to wait for page load after navigating back using browser history\", slog.String(\"error\", err.Error()))\n\t}\n\tnewPageHash, pageState, err := c.isCorrectNavigation(page, action)\n\tif c.diagnostics != nil && pageState != nil {\n\t\tif err := c.diagnostics.LogPageState(pageState, diagnostics.PreActionPageState); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn newPageHash, nil\n}\n\nfunc (c *Crawler) isBackNavigationPossible(page *browser.BrowserPage, originPage *types.PageState) (bool, int, error) {\n\thistory, err := page.GetNavigationHistory()\n\tif err != nil {\n\t\treturn false, 0, err\n\t}\n\tif len(history.Entries) == 0 {\n\t\treturn false, 0, nil\n\t}\n\n\tcurrentIndex := history.CurrentIndex\n\tfor i, entry := range history.Entries {\n\t\tif entry.URL == originPage.URL && originPage.Title == entry.Title {\n\t\t\tstepsBack := currentIndex - i\n\t\t\treturn true, stepsBack, nil\n\t\t}\n\t}\n\treturn false, 0, nil\n}\n\nfunc (c *Crawler) tryShortestPathNavigation(action *types.Action, page *browser.BrowserPage, currentPageHash string) (string, error) {\n\tc.logger.Debug(\"Trying Shortest path to navigate back to origin page\", slog.String(\"action_origin_id\", action.OriginID), slog.String(\"current_page_hash\", currentPageHash))\n\n\tactions, err := c.crawlGraph.ShortestPath(currentPageHash, action.OriginID)\n\tif err != nil {\n\t\tif errors.Is(err, graphlib.ErrTargetNotReachable) {\n\t\t\tc.logger.Debug(\"Target not reachable, reaching from blank state\",\n\t\t\t\tslog.String(\"action_origin_id\", action.OriginID),\n\t\t\t)\n\n\t\t\tactions, err = c.crawlGraph.ShortestPath(emptyPageHash, action.OriginID)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", errors.Wrap(err, \"could not find path to origin page\")\n\t\t\t}\n\t\t} else {\n\t\t\treturn \"\", errors.Wrap(err, \"failed to find shortest path\")\n\t\t}\n\t}\n\tc.logger.Debug(\"Found actions to traverse\",\n\t\tslog.Any(\"actions\", actions),\n\t)\n\tfor _, action := range actions {\n\t\tif err := c.executeCrawlStateAction(action, page); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\tnewPageHash, pageState, err := c.isCorrectNavigation(page, action)\n\tif c.diagnostics != nil && pageState != nil {\n\t\tif err := c.diagnostics.LogPageState(pageState, diagnostics.PreActionPageState); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn newPageHash, nil\n}\n"
  },
  {
    "path": "pkg/engine/headless/crawler/state_test.go",
    "content": "package crawler\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/crawler/normalizer/simhash\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPageFingerprint_Stability(t *testing.T) {\n\n}\n\nfunc TestPageFingerprint(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\thtml1       string\n\t\thtml2       string\n\t\tshouldMatch bool\n\t}{\n\t\t{\n\t\t\tname: \"same page different dynamic content\",\n\t\t\thtml1: `\n                <html>\n                    <head><title>Home</title></head>\n                    <body>\n                        <h2>Welcome John!</h2>\n                        <nav>\n                            <a href=\"/home\">Home</a>\n                            <a href=\"/profile\">Profile</a>\n                        </nav>\n                    </body>\n                </html>`,\n\t\t\thtml2: `\n                <html>\n                    <head><title>Home</title></head>\n                    <body>\n                        <h2>Welcome Jane!</h2>\n                        <nav>\n                            <a href=\"/home\">Home</a>\n                            <a href=\"/profile\">Profile</a>\n                        </nav>\n                    </body>\n                </html>`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname: \"same form different values\",\n\t\t\thtml1: `\n                <form action=\"/login\" method=\"post\">\n                    <input type=\"text\" name=\"username\" value=\"user1\"/>\n                    <input type=\"password\" name=\"password\" value=\"pass1\"/>\n                </form>`,\n\t\t\thtml2: `\n                <form action=\"/login\" method=\"post\">\n                    <input type=\"text\" name=\"username\" value=\"user2\"/>\n                    <input type=\"password\" name=\"password\" value=\"pass2\"/>\n                </form>`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname: \"different error messages\",\n\t\t\thtml1: `\n                <div class=\"alert\">Invalid password</div>`,\n\t\t\thtml2: `\n                <div class=\"alert\">Account locked</div>`,\n\t\t\tshouldMatch: true,\n\t\t},\n\t\t{\n\t\t\tname: \"different page structure\",\n\t\t\thtml1: `\n                <div><h1>Page 1</h1><p>Content</p></div>`,\n\t\t\thtml2: `\n                <div><h2>Page 1</h2><div>Content</div></div>`,\n\t\t\tshouldMatch: false,\n\t\t},\n\t}\n\n\tgetHash := func(html string) (string, error) {\n\t\tstrippedDOM, err := getStrippedDOM(html)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Wrap(err, \"could not get stripped dom\")\n\t\t}\n\t\t// Get sha256 hash of the stripped dom\n\t\treturn sha256Hash(strippedDOM), nil\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\thash1, err := getHash(tt.html1)\n\t\t\tassert.NoError(t, err)\n\n\t\t\thash2, err := getHash(tt.html2)\n\t\t\tassert.NoError(t, err)\n\n\t\t\tif tt.shouldMatch {\n\t\t\t\tassert.Equal(t, hash1, hash2)\n\t\t\t} else {\n\t\t\t\tassert.NotEqual(t, hash1, hash2)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSimHashSimilarity(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\thtml1     string\n\t\thtml2     string\n\t\tthreshold uint8\n\t\tsimilar   bool\n\t}{\n\t\t{\n\t\t\tname:      \"identical pages\",\n\t\t\thtml1:     `<html><body><h1>Hello World</h1><p>Content here</p></body></html>`,\n\t\t\thtml2:     `<html><body><h1>Hello World</h1><p>Content here</p></body></html>`,\n\t\t\tthreshold: 4,\n\t\t\tsimilar:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"pages with minor changes\",\n\t\t\thtml1:     `<html><body><h1>Hello World</h1><p>Content here</p><span>Time: 12:00</span></body></html>`,\n\t\t\thtml2:     `<html><body><h1>Hello World</h1><p>Content here</p><span>Time: 12:01</span></body></html>`,\n\t\t\tthreshold: 4,\n\t\t\tsimilar:   true,\n\t\t},\n\t\t{\n\t\t\tname:      \"pages with different content\",\n\t\t\thtml1:     `<html><body><h1>Hello World</h1><p>Content here</p></body></html>`,\n\t\t\thtml2:     `<html><body><h1>Goodbye World</h1><p>Different content</p><div>Extra stuff</div></body></html>`,\n\t\t\tthreshold: 4,\n\t\t\tsimilar:   false,\n\t\t},\n\t\t{\n\t\t\tname:      \"pages with dynamic IDs\",\n\t\t\thtml1:     `<html><body><div id=\"content-12345\"><h1>Hello</h1></div></body></html>`,\n\t\t\thtml2:     `<html><body><div id=\"content-67890\"><h1>Hello</h1></div></body></html>`,\n\t\t\tthreshold: 4,\n\t\t\tsimilar:   true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Normalize and compute SimHash\n\t\t\tnorm1, err := domNormalizer.Apply(tt.html1)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to normalize html1: %v\", err)\n\t\t\t}\n\n\t\t\tnorm2, err := domNormalizer.Apply(tt.html2)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Failed to normalize html2: %v\", err)\n\t\t\t}\n\n\t\t\thash1 := simhash.Fingerprint(strings.NewReader(norm1), 3)\n\t\t\thash2 := simhash.Fingerprint(strings.NewReader(norm2), 3)\n\n\t\t\tdistance := simhash.Distance(hash1, hash2)\n\t\t\tisSimilar := distance <= tt.threshold\n\n\t\t\tif isSimilar != tt.similar {\n\t\t\t\tt.Errorf(\"Expected similar=%v, got similar=%v (distance=%d)\", tt.similar, isSimilar, distance)\n\t\t\t\tt.Logf(\"Hash1: %064b\", hash1)\n\t\t\t\tt.Logf(\"Hash2: %064b\", hash2)\n\t\t\t\tt.Logf(\"Normalized HTML1:\\n%s\", norm1)\n\t\t\t\tt.Logf(\"Normalized HTML2:\\n%s\", norm2)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/debugger.go",
    "content": "package headless\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n)\n\n// ActiveURL represents a URL currently being processed\ntype ActiveURL struct {\n\tURL       string    `json:\"url\"`\n\tStartTime time.Time `json:\"start_time\"`\n\tDuration  string    `json:\"duration\"`\n\tDepth     int       `json:\"depth\"`\n}\n\n// CrawlDebugger tracks active URLs for debugging\ntype CrawlDebugger struct {\n\tmu         sync.RWMutex\n\tactiveURLs map[string]*ActiveURL\n\thttpServer *http.Server\n}\n\n// NewCrawlDebugger creates a new debugger instance\nfunc NewCrawlDebugger(httpPort int) *CrawlDebugger {\n\tcd := &CrawlDebugger{\n\t\tactiveURLs: make(map[string]*ActiveURL),\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/debug/active-urls\", cd.handleActiveURLs)\n\tmux.HandleFunc(\"/debug/health\", cd.handleHealth)\n\n\tcd.httpServer = &http.Server{\n\t\tAddr:              fmt.Sprintf(\"127.0.0.1:%d\", httpPort),\n\t\tHandler:           mux,\n\t\tReadTimeout:       5 * time.Second,\n\t\tReadHeaderTimeout: 2 * time.Second,\n\t\tWriteTimeout:      5 * time.Second,\n\t\tIdleTimeout:       30 * time.Second,\n\t}\n\n\tgo func() {\n\t\tif err := cd.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {\n\t\t\tfmt.Printf(\"Debug HTTP server error: %v\\n\", err)\n\t\t}\n\t}()\n\n\treturn cd\n}\n\n// StartURL marks a URL as being processed\nfunc (cd *CrawlDebugger) StartURL(url string, depth int) {\n\tif cd == nil {\n\t\treturn\n\t}\n\n\tcd.mu.Lock()\n\tcd.activeURLs[url] = &ActiveURL{\n\t\tURL:       url,\n\t\tStartTime: time.Now(),\n\t\tDepth:     depth,\n\t}\n\tcd.mu.Unlock()\n}\n\n// EndURL marks a URL as finished processing\nfunc (cd *CrawlDebugger) EndURL(url string) {\n\tif cd == nil {\n\t\treturn\n\t}\n\n\tcd.mu.Lock()\n\tdelete(cd.activeURLs, url)\n\tcd.mu.Unlock()\n}\n\n// GetActiveURLs returns currently active URLs with durations\nfunc (cd *CrawlDebugger) GetActiveURLs() []ActiveURL {\n\tif cd == nil {\n\t\treturn nil\n\t}\n\n\tcd.mu.RLock()\n\tdefer cd.mu.RUnlock()\n\n\turls := make([]ActiveURL, 0, len(cd.activeURLs))\n\tnow := time.Now()\n\tfor _, au := range cd.activeURLs {\n\t\tcopy := *au\n\t\tcopy.Duration = now.Sub(au.StartTime).String()\n\t\turls = append(urls, copy)\n\t}\n\treturn urls\n}\n\n// HTTP handlers\nfunc (cd *CrawlDebugger) handleActiveURLs(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tactive := cd.GetActiveURLs()\n\tif err := json.NewEncoder(w).Encode(map[string]interface{}{\n\t\t\"timestamp\":   time.Now().Format(time.RFC3339),\n\t\t\"active_urls\": active,\n\t\t\"count\":       len(active),\n\t}); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t}\n}\n\nfunc (cd *CrawlDebugger) handleHealth(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tif err := json.NewEncoder(w).Encode(map[string]interface{}{\n\t\t\"status\":    \"ok\",\n\t\t\"timestamp\": time.Now().Format(time.RFC3339),\n\t}); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t}\n}\n\nfunc (cd *CrawlDebugger) Close() {\n\tif cd == nil {\n\t\treturn\n\t}\n\n\tif cd.httpServer != nil {\n\t\t_ = cd.httpServer.Close()\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/headless/graph/graph.go",
    "content": "// Package graph implements a Directed Graph for storing\n// state information during crawling of a Web Application.\npackage graph\n\nimport (\n\t\"os\"\n\n\t\"github.com/dominikbraun/graph\"\n\t\"github.com/dominikbraun/graph/draw\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/types\"\n)\n\n// CrawlGraph is a graph for storing state information during crawling\ntype CrawlGraph struct {\n\tgraph graph.Graph[string, types.PageState]\n}\n\nfunc navigationHasherFunc(n types.PageState) string {\n\treturn n.UniqueID\n}\n\n// NewCrawlGraph creates a new CrawlGraph instance\nfunc NewCrawlGraph() *CrawlGraph {\n\treturn &CrawlGraph{\n\t\tgraph: graph.New(navigationHasherFunc, func(t *graph.Traits) {\n\t\t\tt.IsDirected = true\n\t\t\tt.IsRooted = true\n\t\t\tt.IsWeighted = true\n\t\t}),\n\t}\n}\n\nfunc (g *CrawlGraph) GetVertices() []string {\n\tvertices := []string{}\n\tadjacencyMap, err := g.graph.AdjacencyMap()\n\tif err != nil {\n\t\treturn nil\n\t}\n\tfor vertex := range adjacencyMap {\n\t\tvertices = append(vertices, vertex)\n\t}\n\treturn vertices\n}\n\n// AddNavigation adds a navigation to the graph\nfunc (g *CrawlGraph) AddPageState(n types.PageState) error {\n\tvertexAttrs := map[string]string{\n\t\t\"label\": n.URL,\n\t}\n\tif n.IsRoot {\n\t\tvertexAttrs[\"is_root\"] = \"true\"\n\t}\n\n\terr := g.graph.AddVertex(n, func(vp *graph.VertexProperties) {\n\t\tvp.Weight = n.Depth\n\t\tvp.Attributes = vertexAttrs\n\t})\n\tif err != nil {\n\t\tif errors.Is(err, graph.ErrVertexAlreadyExists) {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.Wrap(err, \"could not add vertex to graph\")\n\t}\n\n\tif n.NavigationAction != nil {\n\t\tedgeAttrs := map[string]string{\n\t\t\t\"label\": n.NavigationAction.String(),\n\t\t}\n\n\t\terr = g.graph.AddEdge(n.OriginID, n.UniqueID, func(ep *graph.EdgeProperties) {\n\t\t\tep.Weight = n.Depth\n\t\t\tep.Attributes = edgeAttrs\n\t\t})\n\t\tif err != nil {\n\t\t\tif errors.Is(err, graph.ErrEdgeAlreadyExists) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn errors.Wrapf(err, \"could not add edge to graph: source vertex %s\", n.OriginID)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (g *CrawlGraph) AddEdge(sourceState, targetState string, action *types.Action) error {\n\tif action == nil {\n\t\treturn errors.New(\"add edge: action cannot be nil\")\n\t}\n\tedgeAttrs := map[string]string{\n\t\t\"label\": action.String(),\n\t}\n\terr := g.graph.AddEdge(sourceState, targetState, func(ep *graph.EdgeProperties) {\n\t\tep.Weight = action.Depth\n\t\tep.Attributes = edgeAttrs\n\t})\n\tif err != nil {\n\t\tif errors.Is(err, graph.ErrEdgeAlreadyExists) {\n\t\t\treturn nil\n\t\t}\n\t\treturn errors.Wrap(err, \"could not add edge to graph\")\n\t}\n\treturn nil\n}\n\nfunc (g *CrawlGraph) GetPageState(id string) (*types.PageState, error) {\n\tpageVertex, err := g.graph.Vertex(id)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not get vertex\")\n\t}\n\treturn &pageVertex, nil\n}\n\nfunc (g *CrawlGraph) ShortestPath(sourceState, targetState string) ([]*types.Action, error) {\n\tshortestPath, err := graph.ShortestPath(g.graph, sourceState, targetState)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"could not find shortest path\")\n\t}\n\tactionsSlice := make([]*types.Action, 0, len(shortestPath))\n\tfor _, path := range shortestPath {\n\t\tpageVertex, err := g.graph.Vertex(path)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Wrap(err, \"could not get vertex\")\n\t\t}\n\n\t\tif pageVertex.URL == \"about:blank\" || pageVertex.NavigationAction == nil {\n\t\t\tcontinue\n\t\t}\n\t\tactionsSlice = append(actionsSlice, pageVertex.NavigationAction)\n\t}\n\treturn actionsSlice, nil\n}\n\nfunc (g *CrawlGraph) DrawGraph(file string) error {\n\tf, err := os.Create(file)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"could not create graph file\")\n\t}\n\tdefer func() { _ = f.Close() }()\n\n\treturn draw.DOT(g.graph, f)\n}\n"
  },
  {
    "path": "pkg/engine/headless/headless.go",
    "content": "package headless\n\nimport (\n\t\"log/slog\"\n\t\"net/url\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/lmittmann/tint\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/browser\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/captcha\"\n\t_ \"github.com/projectdiscovery/katana/pkg/engine/headless/captcha/capsolver\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/headless/crawler\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/parser\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\ntype Headless struct {\n\tlogger  *slog.Logger\n\toptions *types.CrawlerOptions\n\n\tdeduplicator *mapsutil.SyncLockMap[string, struct{}]\n\tpathTrie     *utils.PathTrie\n\n\tdebugger *CrawlDebugger\n}\n\n// New returns a new headless crawler instance\nfunc New(options *types.CrawlerOptions) (*Headless, error) {\n\tlogger := newLogger(options)\n\n\theadless := &Headless{\n\t\tlogger:  logger,\n\t\toptions: options,\n\n\t\tdeduplicator: mapsutil.NewSyncLockMap[string, struct{}](),\n\t}\n\tif options.Options.FilterSimilar {\n\t\theadless.pathTrie = utils.NewPathTrie(options.Options.FilterSimilarThreshold)\n\t}\n\n\t// Show crawl debugger if verbose is enabled\n\tif options.Options.Verbose {\n\t\theadless.debugger = NewCrawlDebugger(8089)\n\t}\n\n\treturn headless, nil\n}\n\nfunc newLogger(options *types.CrawlerOptions) *slog.Logger {\n\tif options.Logger != nil {\n\t\treturn options.Logger\n\t}\n\n\twriter := os.Stderr\n\n\t// set global logger with custom options\n\tlevel := slog.LevelInfo\n\tif options.Options.Debug {\n\t\tlevel = slog.LevelDebug\n\t}\n\tlogger := slog.New(\n\t\ttint.NewHandler(writer, &tint.Options{\n\t\t\tLevel:      level,\n\t\t\tTimeFormat: time.Kitchen,\n\t\t}),\n\t)\n\treturn logger\n}\n\nfunc validateScopeFunc(h *Headless, URL string) browser.ScopeValidator {\n\tparsedURL, err := url.Parse(URL)\n\tif err != nil {\n\t\treturn func(string) bool { return true }\n\t}\n\trootHostname := parsedURL.Hostname()\n\n\treturn func(s string) bool {\n\t\tif h.options.ScopeManager == nil {\n\t\t\treturn true\n\t\t}\n\t\tparsed, err := url.Parse(s)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tvalidated, err := h.options.ScopeManager.Validate(parsed, rootHostname)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn validated\n\t}\n}\n\n// Crawl executes the headless crawling on a given URL\nfunc (h *Headless) Crawl(URL string) error {\n\tif h.debugger != nil {\n\t\th.debugger.StartURL(URL, 0)\n\t}\n\tdefer func() {\n\t\tif h.debugger != nil {\n\t\t\th.debugger.EndURL(URL)\n\t\t}\n\t}()\n\n\tscopeValidator := validateScopeFunc(h, URL)\n\n\tcrawlOpts := crawler.Options{\n\t\tChromiumPath:      h.options.Options.SystemChromePath,\n\t\tMaxDepth:          h.options.Options.MaxDepth,\n\t\tShowBrowser:       h.options.Options.ShowBrowser,\n\t\tMaxCrawlDuration:  h.options.Options.CrawlDuration,\n\t\tMaxFailureCount:   h.options.Options.MaxFailureCount,\n\t\tNoSandbox:         h.options.Options.HeadlessNoSandbox,\n\t\tProxy:             h.options.Options.Proxy,\n\t\tMaxBrowsers:       1,\n\t\tPageMaxTimeout:    30 * time.Second,\n\t\tScopeValidator:    scopeValidator,\n\t\tAutomaticFormFill: h.options.Options.AutomaticFormFill,\n\t\tRequestCallback: func(rr *output.Result) {\n\t\t\tif rr == nil || rr.Request == nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif scopeValidator != nil && !scopeValidator(rr.Request.URL) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnavigationRequests := h.performAdditionalAnalysis(rr)\n\t\t\tfor _, req := range navigationRequests {\n\t\t\t\tif err := h.options.OutputWriter.Write(req); err != nil {\n\t\t\t\t\th.logger.Debug(\"failed to write navigation result\",\n\t\t\t\t\t\tslog.String(\"url\", func() string {\n\t\t\t\t\t\t\tif req != nil && req.Request != nil {\n\t\t\t\t\t\t\t\treturn req.Request.URL\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn \"\"\n\t\t\t\t\t\t}()),\n\t\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif rr.Response != nil {\n\t\t\t\trr.Response.KnowledgeBase = h.options.ClassifyPage(rr.Response.Body)\n\t\t\t\trr.Response.Raw = \"\"\n\t\t\t\trr.Response.Body = \"\"\n\t\t\t}\n\t\t\tif err := h.options.OutputWriter.Write(rr); err != nil {\n\t\t\t\th.logger.Debug(\"failed to write result\",\n\t\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t\t)\n\t\t\t}\n\t\t},\n\t\tLogger:              h.logger,\n\t\tChromeUser:          h.options.ChromeUser,\n\t\tEnableDiagnostics:   h.options.Options.EnableDiagnostics,\n\t\tTrace:               h.options.Options.EnableDiagnostics,\n\t\tCookieConsentBypass: true,\n\t}\n\n\tif provider := h.options.Options.CaptchaSolverProvider; provider != \"\" {\n\t\tgologger.Debug().Msgf(\"captcha solver enabled: provider=%s\", provider)\n\t\thandler, err := captcha.NewHandler(provider, h.options.Options.CaptchaSolverAPIKey)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"captcha handler init failed: %s\", err)\n\t\t} else {\n\t\t\tcrawlOpts.CaptchaHandler = handler\n\t\t}\n\t}\n\n\t// TODO: Make the crawling multi-threaded. Right now concurrency is hardcoded to 1.\n\n\theadlessCrawler, err := crawler.New(crawlOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer headlessCrawler.Close()\n\n\tif err = headlessCrawler.Crawl(URL); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (h *Headless) Close() error {\n\tif h.debugger != nil {\n\t\th.debugger.Close()\n\t}\n\treturn nil\n}\n\nfunc (h *Headless) performAdditionalAnalysis(rr *output.Result) []*output.Result {\n\tresponseParser := parser.NewResponseParser()\n\tnewNavigations := responseParser.ParseResponse(rr.Response)\n\n\tnavigationRequests := make([]*output.Result, 0)\n\tfor _, resp := range newNavigations {\n\t\tdedupKey := resp.URL\n\t\tif h.options.Options.FilterSimilar {\n\t\t\tdedupKey = utils.FingerprintURL(dedupKey, h.pathTrie)\n\t\t}\n\t\tif _, ok := h.deduplicator.Get(dedupKey); ok {\n\t\t\tcontinue\n\t\t}\n\t\tif err := h.deduplicator.Set(dedupKey, struct{}{}); err != nil {\n\t\t\th.logger.Debug(\"deduplicator set failed\",\n\t\t\t\tslog.String(\"url\", resp.URL),\n\t\t\t\tslog.String(\"error\", err.Error()),\n\t\t\t)\n\t\t\tcontinue\n\t\t}\n\n\t\tnavigationRequests = append(navigationRequests, &output.Result{\n\t\t\tRequest: resp,\n\t\t})\n\t}\n\treturn navigationRequests\n}\n"
  },
  {
    "path": "pkg/engine/headless/js/js.go",
    "content": "package js\n\nimport (\n\t_ \"embed\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\t//go:embed utils.js\n\tutilsJavascriptBundle string\n\n\t//go:embed page-init.js\n\tpageInitJavascriptBundle string\n)\n\n// InitJavascriptEnv injects the necessary javascript code into the browser\nfunc InitJavascriptEnv(page *rod.Page) error {\n\tif _, err := page.EvalOnNewDocument(utilsJavascriptBundle); err != nil {\n\t\treturn errors.Wrap(err, \"failed to inject utils.js\")\n\t}\n\tif _, err := page.EvalOnNewDocument(pageInitJavascriptBundle); err != nil {\n\t\treturn errors.Wrap(err, \"failed to inject page-init.js\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/engine/headless/js/page-init.js",
    "content": "// This script initializes the page and hooks up event listeners\n// and other interesting stuff needed to make the crawling work.\n//\n// Actions performed:\n//\n// 1. Hook addTargetListener to capture all the event listeners added on the page.\n//    These are accessible via window.__eventListeners\n// 2. Hook window.open to capture all the opened pages.\n//    These are accessible via window.__navigatedLinks\n// 3. Hook setTimeout and setInterval to speed up delayed actions\n// 4. Hook form reset to prevent the form from being reset\n// 5. Hook window.close to prevent the page from being closed\n// 6. Hook history pushState and replaceState for new links\n// 7. Add event listener for hashchange to identify new navigations\n// 8. TODO: Hook inline event listeners so that layer0 event listeners can be tracked as well\n(function pageInitAndHook() {\n    const markElementReadonlyProperties = {\n      writable: false,\n      configurable: false,\n    };\n  \n    // hookNavigatedLinkSinks hooks the navigated link sinks\n    // on the page to capture all the navigated links.\n    function hookNavigatedLinkSinks() {\n      window.__navigatedLinks = [];\n  \n      // Hook history.pushState and history.replaceState to capture all the navigated links\n      const __origPushState = window.history.pushState.bind(window.history);\n      const __origReplaceState = window.history.replaceState.bind(window.history);\n      function __wrappedPushState(a, b, c) {\n        try { window.__navigatedLinks.push({ url: c, source: \"history.pushState\" }); } catch (_) {}\n        return __origPushState(a, b, c);\n      }\n      function __wrappedReplaceState(a, b, c) {\n        try { window.__navigatedLinks.push({ url: c, source: \"history.replaceState\" }); } catch (_) {}\n        return __origReplaceState(a, b, c);\n      }\n      Object.defineProperty(window.history, \"pushState\", { value: __wrappedPushState, writable: false, configurable: false });\n      Object.defineProperty(window.history, \"replaceState\", { value: __wrappedReplaceState, writable: false, configurable: false });\n      // Hook window.open to capture all the opened pages\n      const __origOpen = window.open.bind(window);\n      function __wrappedOpen(url, ...rest) {\n        try { window.__navigatedLinks.push({ url, source: \"window.open\" }); } catch (_) {}\n        return __origOpen(url, ...rest);\n      }\n      Object.defineProperty(window, \"open\", { value: __wrappedOpen, writable: false, configurable: false });\n  \n      // Add event listener for hashchange\n      window.addEventListener(\"hashchange\", function () {\n        window.__navigatedLinks.push({\n          url: document.location.href,\n          source: \"hashchange\",\n        });\n      });\n  \n      const __OrigWebSocket = window.WebSocket;\n      function __WrappedWebSocket(url, protocols) {\n        try { window.__navigatedLinks.push({ url, source: \"websocket\" }); } catch (_) {}\n        return Reflect.construct(__OrigWebSocket, [url, protocols], new.target || __WrappedWebSocket);\n      }\n      __WrappedWebSocket.prototype = __OrigWebSocket.prototype;\n      Object.setPrototypeOf(__WrappedWebSocket, __OrigWebSocket);\n      Object.defineProperty(window, \"WebSocket\", { value: __WrappedWebSocket, writable: false, configurable: false });\n\n      const __OrigEventSource = window.EventSource;\n      function __WrappedEventSource(url, eventSourceInitDict) {\n        try { window.__navigatedLinks.push({ url, source: \"eventsource\" }); } catch (_) {}\n        return Reflect.construct(__OrigEventSource, [url, eventSourceInitDict], new.target || __WrappedEventSource);\n      }\n      __WrappedEventSource.prototype = __OrigEventSource.prototype;\n      Object.setPrototypeOf(__WrappedEventSource, __OrigEventSource);\n      Object.defineProperty(window, \"EventSource\", { value: __WrappedEventSource, writable: false, configurable: false });\n  \n      const __origFetch = window.fetch.bind(window);\n      function __wrappedFetch(...args) {\n        const url = args[0] instanceof Request ? args[0].url : args[0];\n        try { window.__navigatedLinks.push({ url: url, source: \"fetch\" }); } catch (_) {}\n        return __origFetch(...args);\n      }\n      Object.defineProperty(window, \"fetch\", { value: __wrappedFetch, writable: false, configurable: false });\n    }\n  \n    // hookMiscellaneousUtilities performs miscellaneous hooks\n    // on the page to prevent certain actions from happening\n    // and to speed up certain actions.\n    function hookMiscellaneousUtilities() {\n      // Hook form reset to conditionally prevent the form from being reset\n      const __origFormReset = HTMLFormElement.prototype.reset;\n      const __wrappedFormReset = function (...args) {\n        if (window.__katanaHooksOptions?.preventFormReset === true) {\n          try { console.log(\"[hook] cancel reset form\"); } catch (_) {}\n          return;\n        }\n        return __origFormReset.apply(this, args);\n      };\n      Object.defineProperty(\n        HTMLFormElement.prototype,\n        \"reset\",\n        { value: __wrappedFormReset, writable: false, configurable: false }\n      );\n  \n      // Hook window.close to prevent the page from being closed\n      const __wrappedClose = function () {\n        console.log(\"[hook] trying to close page.\");\n      };\n      Object.defineProperty(window, \"close\", { value: __wrappedClose, writable: false, configurable: false });\n  \n      // Hook setTimeout and setInterval to speed up delayed actions\n      // on the page. This is useful where there is some request happening\n      // on the page after a delay or some animation happening after a delay.\n      const __origSetTimeout = window.setTimeout;\n      const __origSetInterval = window.setInterval;\n\n      const speedUpFactor = 0.1;\n\n      function __wrappedSetTimeout(callback, delay, ...args) {\n        return __origSetTimeout(callback, delay * speedUpFactor, ...args);\n      }\n      function __wrappedSetInterval(callback, delay, ...args) {\n        return __origSetInterval(callback, delay * speedUpFactor, ...args);\n      }\n      Object.defineProperty(window, \"setTimeout\", { value: __wrappedSetTimeout, writable: false, configurable: false });\n      Object.defineProperty(window, \"setInterval\", { value: __wrappedSetInterval, writable: false, configurable: false });\n    }\n  \n    // hookAddEventListener hooks the addTargetListener to capture\n    // all the event listeners added on the page\n    function hookAddEventListener() {\n      const originalAddEventListener = Element.prototype.addEventListener;\n  \n      window.__eventListeners = [];\n      Element.prototype.addEventListener = function (type, listener, options) {\n        // Ensure `this` is a valid element and has the necessary properties\n        if (!this || !this.tagName) {\n          return originalAddEventListener.call(this, type, listener, options);\n        }\n  \n        if (this.tagName == \"BODY\") {\n          return originalAddEventListener.call(this, type, listener, options);\n        }\n        let item = {\n          element: {\n            tagName: this.tagName,\n            id: this.id,\n            classes: this.className,\n            outerHTML: this.outerHTML.slice(0, 100), // Capture a snippet of the element's outerHTML\n            xpath: window.getXPath(this),\n            cssSelector: window.getCssPath(this),\n            attributes: window.getElementAttributes(this),\n            textContent: this.textContent.trim(),\n            hidden: this.hidden,\n            name: this.name,\n            type: this.type,\n            value: this.value,\n          },\n          type: type,\n          listener: listener.toString(),\n          options: options || {},\n        };\n        console.log(\"[hook] got event listener\", item);\n        window.__eventListeners.push(item);\n        return originalAddEventListener.call(this, type, listener, options);\n      };\n    }\n  \n    // Main hook initialization part\n    const __opts = window.__katanaHooksOptions || { hooked: false };\n    try { if (__opts.hooked === true) hookAddEventListener(); } catch (_) {}\n    try { if (__opts.hooked === true) hookNavigatedLinkSinks(); } catch (_) {}\n    try { if (__opts.hooked === true) hookMiscellaneousUtilities(); } catch (_) {}\n  })();\n  "
  },
  {
    "path": "pkg/engine/headless/js/utils.js",
    "content": "// This file contains utility JS functions that are utilised by\n// the main crawling JS code to perform actions.\n(function initUtilityFunctions() {\n    // getElementAttributes returns the attributes of an element\n    window.getElementAttributes = function (element) {\n      const attrs = {};\n      for (let attr of element.attributes) {\n        attrs[attr.name] = attr.value;\n      }\n      return attrs;\n    };\n  \n    // _elementDataFromElement returns the data for an element\n    window._elementDataFromElement = function (el) {\n      return {\n        tagName: el.tagName,\n        id: el.id,\n        classes: typeof el.className === 'string' ? el.className : Array.from(el.classList).join(' '),\n        attributes: window.getElementAttributes(el),\n        hidden: el.hidden,\n        outerHTML: el.outerHTML,\n        name: el.name,\n        type: el.type,\n        value: el.value != null ? String(el.value) : '',\n        textContent: el.textContent.trim(),\n        xpath: window.getXPath(el),\n        cssSelector: window.getCssPath(el),\n      };\n    };\n  \n    // getAllElements returns all the elements for a query\n    // selector on the page\n    window.getAllElements = function (selector) {\n      try {\n        const nodes = document.querySelectorAll(selector);\n        return Array.from(nodes).map((el) => _elementDataFromElement(el));\n      } catch (_) {\n        return [];\n      }\n    };\n\n    window.getElementFromXPath = function (xpath) {\n      try {\n        const element = document\n          .evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)\n          .singleNodeValue;\n        return element ? _elementDataFromElement(element) : null;\n      } catch (_) {\n        return null;\n      }\n    }\n  \n    // getAllElementsWithEventListeners returns all the elements\n    // on the page along with their event listeners\n    // TODO: Is it optimized? or do we need to do something else?\n    window.getAllElementsWithEventListeners = function () {\n      const elements = document.querySelectorAll(\"*\");\n      const elementsWithListeners = [];\n      for (let el of elements) {\n        const listeners = getEventListeners(el);\n        if (listeners && listeners.length) {\n          elementsWithListeners.push({\n            element: _elementDataFromElement(el),\n            listeners: listeners,\n          });\n        }\n      }\n      return elementsWithListeners;\n    };\n  \n    // getEventListeners returns all the event listeners\n    // attached to an element\n    function getEventListeners(element) {\n      const listeners = [];\n      for (let event in element) {\n        if (event.startsWith(\"on\")) {\n          const listener = element[event];\n          if (typeof listener === \"function\") {\n            listeners.push({\n              type: event,\n              listener: listener.toString(),\n            });\n          }\n        }\n      }\n      return listeners;\n    }\n  \n    // getAllForms returns all the forms on the page\n    // along with their elements\n    window.getAllForms = function () {\n      const forms = document.querySelectorAll(\"form\");\n      const pseudoForms = document.querySelectorAll(\"div.form\");\n      \n      const allForms = [...forms, ...pseudoForms];\n      return Array.from(allForms).map((form) => ({\n        tagName: form.tagName,\n        id: form.id,\n        classes: typeof form.className === 'string' ? form.className : Array.from(form.classList).join(' '),\n        attributes: window.getElementAttributes(form),\n        outerHTML: form.outerHTML,\n        action: form.action ? String(form.action) : '',\n        method: form.method,\n        xpath: window.getXPath(form),\n        cssSelector: window.getCssPath(form),\n        elements: form.elements ? \n          Array.from(form.elements).map((el) => _elementDataFromElement(el)) :\n          Array.from(form.querySelectorAll('input, select, textarea, button')).map((el) => _elementDataFromElement(el))\n      }));\n    };\n  \n    // Copyright (C) Chrome Authors\n    // The below code is part of the Chrome DevTools project\n    // and is adapted from there.\n  \n    // Utility to get the CSS selector path for an element.\n    window.getCssPath = function (node, optimized = false) {\n      if (node.nodeType !== Node.ELEMENT_NODE) return \"\";\n  \n      const steps = [];\n      let contextNode = node;\n      while (contextNode) {\n        const step = window._cssPathStep(\n          contextNode,\n          optimized,\n          contextNode === node\n        );\n        if (!step) break; // Error - bail out early.\n        steps.push(step.value);\n        if (step.optimized) break;\n        contextNode = contextNode.parentNode;\n      }\n  \n      steps.reverse();\n      return steps.join(\" > \");\n    };\n  \n    // Utility to get the XPath for an element.\n    window.getXPath = function (node, optimized = false) {\n      if (node.nodeType === Node.DOCUMENT_NODE) return \"/\";\n  \n      const steps = [];\n      let contextNode = node;\n      while (contextNode) {\n        const step = window._xPathValue(contextNode, optimized);\n        if (!step) break; // Error - bail out early.\n        steps.push(step.value);\n        if (step.optimized) break;\n        contextNode = contextNode.parentNode;\n      }\n  \n      steps.reverse();\n      return (steps.length && steps[0].optimized ? \"\" : \"/\") + steps.join(\"/\");\n    };\n  \n    // Helper to create a step in the CSS path.\n    window._cssPathStep = function (node, optimized, isTargetNode) {\n      if (node.nodeType !== Node.ELEMENT_NODE) return null;\n  \n      const id = node.getAttribute(\"id\");\n      if (optimized) {\n        if (id) return { value: `#${id}`, optimized: true };\n        const nodeNameLower = node.nodeName.toLowerCase();\n        if (\n          nodeNameLower === \"body\" ||\n          nodeNameLower === \"head\" ||\n          nodeNameLower === \"html\"\n        )\n          return { value: node.nodeName, optimized: true };\n      }\n      const nodeName = node.nodeName;\n  \n      if (id) return { value: `${nodeName}#${id}`, optimized: true };\n      const parent = node.parentNode;\n      if (!parent || parent.nodeType === Node.DOCUMENT_NODE)\n        return { value: nodeName, optimized: true };\n  \n      const prefixedOwnClassNamesArray = window.prefixedElementClassNames(node);\n      let needsClassNames = false;\n      let needsNthChild = false;\n      let ownIndex = -1;\n      let elementIndex = -1;\n      const siblings = parent.children;\n      for (\n        let i = 0;\n        (ownIndex === -1 || !needsNthChild) && i < siblings.length;\n        ++i\n      ) {\n        const sibling = siblings[i];\n        if (sibling.nodeType !== Node.ELEMENT_NODE) continue;\n        elementIndex += 1;\n        if (sibling === node) {\n          ownIndex = elementIndex;\n          continue;\n        }\n        if (needsNthChild) continue;\n        if (sibling.nodeName.toLowerCase() !== nodeName.toLowerCase()) continue;\n  \n        needsClassNames = true;\n        const ownClassNames = new Set(prefixedOwnClassNamesArray);\n        if (!ownClassNames.size) {\n          needsNthChild = true;\n          continue;\n        }\n        const siblingClassNamesArray = window.prefixedElementClassNames(sibling);\n        for (let j = 0; j < siblingClassNamesArray.length; ++j) {\n          const siblingClass = siblingClassNamesArray[j];\n          if (!ownClassNames.has(siblingClass)) continue;\n          ownClassNames.delete(siblingClass);\n          if (!ownClassNames.size) {\n            needsNthChild = true;\n            break;\n          }\n        }\n      }\n  \n      let result = nodeName;\n      if (\n        isTargetNode &&\n        nodeName.toLowerCase() === \"input\" &&\n        node.getAttribute(\"type\") &&\n        !node.getAttribute(\"id\") &&\n        !node.getAttribute(\"class\")\n      )\n        result += '[type=\"' + node.getAttribute(\"type\") + '\"]';\n      if (needsNthChild) {\n        result += `:nth-child(${ownIndex + 1})`;\n      } else if (needsClassNames) {\n        for (const prefixedName of prefixedOwnClassNamesArray)\n          result += \".\" + window.escapeIdentifierIfNeeded(prefixedName.substr(1));\n      }\n  \n      return { value: result, optimized: false };\n    };\n  \n    // Helper to get class names prefixed with '$' for mapping purposes.\n    window.prefixedElementClassNames = function (node) {\n      const classAttribute = node.getAttribute(\"class\");\n      if (!classAttribute) return [];\n  \n      return classAttribute\n        .split(/\\s+/g)\n        .filter(Boolean)\n        .map(function (name) {\n          return \"$\" + name;\n        });\n    };\n  \n    // Helper to escape identifiers for use in CSS selectors.\n    window.escapeIdentifierIfNeeded = function (ident) {\n      if (window.isCSSIdentifier(ident)) return ident;\n      const shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident);\n      const lastIndex = ident.length - 1;\n      return ident.replace(/./g, function (c, i) {\n        return (shouldEscapeFirst && i === 0) || !window.isCSSIdentChar(c)\n          ? window.escapeAsciiChar(c, i === lastIndex)\n          : c;\n      });\n    };\n  \n    // Helper to determine if a character is valid in a CSS identifier.\n    window.isCSSIdentChar = function (c) {\n      if (/[a-zA-Z0-9_-]/.test(c)) return true;\n      return c.charCodeAt(0) >= 0xa0;\n    };\n  \n    // Helper to determine if a string is a valid CSS identifier.\n    window.isCSSIdentifier = function (value) {\n      return /^-{0,2}[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value);\n    };\n  \n    // Helper to escape ASCII characters for use in CSS selectors.\n    window.escapeAsciiChar = function (c, isLast) {\n      return (\n        \"\\\\\" + c.charCodeAt(0).toString(16).padStart(2, \"0\") + (isLast ? \"\" : \" \")\n      );\n    };\n  \n    // Helper to get the XPath step for a node.\n    window._xPathValue = function (node, optimized) {\n      let ownValue;\n      const ownIndex = window._xPathIndex(node);\n      if (ownIndex === -1) return null;\n  \n      switch (node.nodeType) {\n        case Node.ELEMENT_NODE:\n          if (optimized && node.getAttribute(\"id\"))\n            return {\n              value: `//*[@id=\"${node.getAttribute(\"id\")}\"]`,\n              optimized: true,\n            };\n          ownValue = node.localName;\n          break;\n        case Node.ATTRIBUTE_NODE:\n          ownValue = \"@\" + node.nodeName;\n          break;\n        case Node.TEXT_NODE:\n        case Node.CDATA_SECTION_NODE:\n          ownValue = \"text()\";\n          break;\n        case Node.PROCESSING_INSTRUCTION_NODE:\n          ownValue = \"processing-instruction()\";\n          break;\n        case Node.COMMENT_NODE:\n          ownValue = \"comment()\";\n          break;\n        case Node.DOCUMENT_NODE:\n          ownValue = \"\";\n          break;\n        default:\n          ownValue = \"\";\n          break;\n      }\n  \n      if (ownIndex > 0) ownValue += `[${ownIndex}]`;\n  \n      return { value: ownValue, optimized: node.nodeType === Node.DOCUMENT_NODE };\n    };\n  \n    // Helper to get the XPath index for a node.\n    window._xPathIndex = function (node) {\n      function areNodesSimilar(left, right) {\n        if (left === right) return true;\n  \n        if (\n          left.nodeType === Node.ELEMENT_NODE &&\n          right.nodeType === Node.ELEMENT_NODE\n        )\n          return left.localName === right.localName;\n  \n        if (left.nodeType === right.nodeType) return true;\n  \n        const leftType =\n          left.nodeType === Node.CDATA_SECTION_NODE\n            ? Node.TEXT_NODE\n            : left.nodeType;\n        const rightType =\n          right.nodeType === Node.CDATA_SECTION_NODE\n            ? Node.TEXT_NODE\n            : right.nodeType;\n        return leftType === rightType;\n      }\n  \n      const siblings = node.parentNode ? node.parentNode.children : null;\n      if (!siblings) return 0;\n      let hasSameNamedElements = false;\n      for (let i = 0; i < siblings.length; ++i) {\n        if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {\n          hasSameNamedElements = true;\n          break;\n        }\n      }\n      if (!hasSameNamedElements) return 0;\n      let ownIndex = 1;\n      for (let i = 0; i < siblings.length; ++i) {\n        if (areNodesSimilar(node, siblings[i])) {\n          if (siblings[i] === node) return ownIndex;\n          ++ownIndex;\n        }\n      }\n      return -1;\n    };\n  })();\n  "
  },
  {
    "path": "pkg/engine/headless/types/types.go",
    "content": "package types\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n)\n\nvar (\n\tIsDiagnosticEnabled = false\n)\n\n// PageState represents a page in the state of the\n// web application as determined by the crawler.\n// It represents the vertex of the crawl graph\ntype PageState struct {\n\tUniqueID    string `json:\"unique_id,omitempty\"`\n\tSimHash     uint64 `json:\"sim_hash,omitempty\"`\n\tOriginID    string `json:\"origin_id,omitempty\"`\n\tURL         string `json:\"url,omitempty\"`\n\tTitle       string `json:\"title,omitempty\"`\n\tDOM         string `json:\"dom,omitempty\"`\n\tStrippedDOM string `json:\"stripped_dom,omitempty\"`\n\tDepth       int    `json:\"depth,omitempty\"`\n\tIsRoot      bool   `json:\"is_root,omitempty\"`\n\n\t// NavigationAction is actions taken to reach this state\n\tNavigationAction *Action `json:\"navigation_actions,omitempty\"`\n}\n\n// Action is a action taken in the browser\ntype Action struct {\n\tOriginID string       `json:\"origin_id,omitempty\"`\n\tType     ActionType   `json:\"type,omitempty\"`\n\tInput    string       `json:\"input,omitempty\"`\n\tElement  *HTMLElement `json:\"element,omitempty\"`\n\tForm     *HTMLForm    `json:\"form,omitempty\"`\n\tDepth    int          `json:\"depth,omitempty\"`\n\tResultID string       `json:\"result_id,omitempty\"`\n}\n\nfunc (a *Action) Hash() string {\n\tif a.Element != nil {\n\t\treturn a.Element.Hash()\n\t}\n\tif a.Form != nil {\n\t\treturn a.Form.Hash()\n\t}\n\treturn string(a.Type) + \"|\" + a.Input + \"|\" + a.OriginID\n}\n\nfunc (a *Action) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(string(a.Type))\n\tif a.Type == ActionTypeLoadURL {\n\t\tfmt.Fprintf(&builder, \" %s\", a.Input)\n\t}\n\tif a.Element != nil {\n\t\tfmt.Fprintf(&builder, \" on %s\", a.Element)\n\t}\n\tvalue := builder.String()\n\treturn value\n}\n\ntype ActionType string\n\nconst (\n\tActionTypeUnknown         ActionType = \"unknown\"\n\tActionTypeLoadURL         ActionType = \"load_url\"\n\tActionTypeExecuteJS       ActionType = \"execute_js\"\n\tActionTypeLeftClick       ActionType = \"left_click\"\n\tActionTypeLeftClickDown   ActionType = \"left_click_down\"\n\tActionTypeLeftClickUp     ActionType = \"left_click_up\"\n\tActionTypeRightClick      ActionType = \"right_click\"\n\tActionTypeDoubleClick     ActionType = \"double_click\"\n\tActionTypeScroll          ActionType = \"scroll\"\n\tActionTypeSendKeys        ActionType = \"send_keys\"\n\tActionTypeKeyUp           ActionType = \"key_up\"\n\tActionTypeKeyDown         ActionType = \"key_down\"\n\tActionTypeHover           ActionType = \"hover\"\n\tActionTypeFocus           ActionType = \"focus\"\n\tActionTypeBlur            ActionType = \"blur\"\n\tActionTypeMouseOverAndOut ActionType = \"mouse_over_and_out\"\n\tActionTypeMouseWheel      ActionType = \"mouse_wheel\"\n\tActionTypeFillForm        ActionType = \"fill_form\"\n\tActionTypeWait            ActionType = \"wait\"\n\tActionTypeRedirect        ActionType = \"redirect\"\n\tActionTypeSubRequest      ActionType = \"sub_request\"\n)\n\nfunc ActionFromEventListener(listener *EventListener) *Action {\n\tvar actionType ActionType\n\n\tswitch listener.Type {\n\tcase \"click\":\n\t\tactionType = ActionTypeLeftClick\n\tcase \"mousedown\":\n\t\tactionType = ActionTypeLeftClickDown\n\tcase \"mouseup\":\n\t\tactionType = ActionTypeLeftClickUp\n\tcase \"keydown\":\n\t\tactionType = ActionTypeKeyDown\n\tcase \"keyup\":\n\t\tactionType = ActionTypeKeyUp\n\tcase \"focus\":\n\t\tactionType = ActionTypeFocus\n\tcase \"blur\":\n\t\tactionType = ActionTypeBlur\n\tcase \"scroll\":\n\t\tactionType = ActionTypeScroll\n\tcase \"dblclick\":\n\t\tactionType = ActionTypeDoubleClick\n\tcase \"contextmenu\":\n\t\tactionType = ActionTypeRightClick\n\tcase \"mouseover\", \"mouseout\":\n\t\tactionType = ActionTypeMouseOverAndOut\n\tcase \"wheel\":\n\t\tactionType = ActionTypeMouseWheel\n\tdefault:\n\t\tactionType = ActionTypeUnknown\n\t}\n\n\treturn &Action{\n\t\tType:    actionType,\n\t\tElement: listener.Element,\n\t}\n}\n\n// HTMLElement represents a DOM element\ntype HTMLElement struct {\n\tTagName     string            `json:\"tagName,omitempty\"`\n\tID          string            `json:\"id,omitempty\"`\n\tClasses     string            `json:\"classes,omitempty\"`\n\tAttributes  map[string]string `json:\"attributes,omitempty\"`\n\tHidden      bool              `json:\"hidden,omitempty\"`\n\tOuterHTML   string            `json:\"outerHTML,omitempty\"`\n\tType        string            `json:\"type,omitempty\"`\n\tValue       string            `json:\"value,omitempty\"`\n\tCSSSelector string            `json:\"cssSelector,omitempty\"`\n\tXPath       string            `json:\"xpath,omitempty\"`\n\tTextContent string            `json:\"textContent,omitempty\"`\n\tMD5Hash     string            `json:\"md5Hash,omitempty\"`\n}\n\nfunc (e *HTMLElement) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(e.TagName)\n\tif e.ID != \"\" {\n\t\tfmt.Fprintf(&builder, \"#%s\", e.ID)\n\t}\n\tif e.Classes != \"\" {\n\t\tfmt.Fprintf(&builder, \".%s\", e.Classes)\n\t}\n\tif e.TextContent != \"\" {\n\t\ttrimmedContent := strings.Trim(e.TextContent, \" \\n\\r\\t\")\n\t\tfmt.Fprintf(&builder, \" (%s)\", trimmedContent)\n\t}\n\tvalue := builder.String()\n\treturn value\n}\n\nfunc (e *HTMLElement) Hash() string {\n\thasher := md5.New()\n\n\tvar parts []string\n\t// Use stable identifiers\n\tparts = append(parts, e.TagName)\n\n\tif e.ID != \"\" {\n\t\tparts = append(parts, \"id:\"+e.ID)\n\t}\n\tif e.Classes != \"\" {\n\t\tparts = append(parts, \"class:\"+e.Classes)\n\t}\n\n\t// Add stable attributes\n\tstableAttrs := getStableAttributes(e.Attributes)\n\tfor _, k := range stableAttrs {\n\t\tparts = append(parts, fmt.Sprintf(\"%s:%s\", k, e.Attributes[k]))\n\t}\n\n\thashInput := strings.Join(parts, \"|\")\n\tif IsDiagnosticEnabled {\n\t\tfmt.Fprintf(os.Stderr, \"[diagnostic] Element hash input: %s\\n\", hashInput)\n\t}\n\thasher.Write([]byte(hashInput))\n\treturn hex.EncodeToString(hasher.Sum(nil))\n}\n\n// HTMLForm represents a form element\ntype HTMLForm struct {\n\tTagName     string            `json:\"tagName,omitempty\"`\n\tID          string            `json:\"id,omitempty\"`\n\tClasses     string            `json:\"classes,omitempty\"`\n\tAttributes  map[string]string `json:\"attributes,omitempty\"`\n\tHidden      bool              `json:\"hidden,omitempty\"`\n\tOuterHTML   string            `json:\"outerHTML,omitempty\"`\n\tAction      string            `json:\"action,omitempty\"`\n\tMethod      string            `json:\"method,omitempty\"`\n\tElements    []*HTMLElement    `json:\"elements,omitempty\"`\n\tCSSSelector string            `json:\"cssSelector,omitempty\"`\n\tXPath       string            `json:\"xpath,omitempty\"`\n}\n\nfunc (f *HTMLForm) Hash() string {\n\thasher := md5.New()\n\n\tvar parts []string\n\tparts = append(parts, f.TagName)\n\n\tif f.ID != \"\" {\n\t\tparts = append(parts, \"id:\"+f.ID)\n\t}\n\tif f.Classes != \"\" {\n\t\tparts = append(parts, \"class:\"+f.Classes)\n\t}\n\n\t// Add stable attributes\n\tstableAttrs := getStableAttributes(f.Attributes)\n\tfor _, k := range stableAttrs {\n\t\tparts = append(parts, fmt.Sprintf(\"%s:%s\", k, f.Attributes[k]))\n\t}\n\tparts = append(parts, fmt.Sprintf(\"action:%s\", f.Action), fmt.Sprintf(\"method:%s\", f.Method))\n\n\t// Include hashes of form elements\n\tfor _, element := range f.Elements {\n\t\tparts = append(parts, element.Hash())\n\t}\n\n\thashInput := strings.Join(parts, \"|\")\n\tif IsDiagnosticEnabled {\n\t\tfmt.Fprintf(os.Stderr, \"[diagnostic] Form hash input: %s\\n\", hashInput)\n\t}\n\thasher.Write([]byte(hashInput))\n\treturn hex.EncodeToString(hasher.Sum(nil))\n}\n\n// getStableAttributes returns a sorted slice of attribute keys that are considered stable.\nfunc getStableAttributes(attrs map[string]string) []string {\n\tstableKeys := map[string]struct{}{\n\t\t\"id\":          {},\n\t\t\"name\":        {},\n\t\t\"type\":        {},\n\t\t\"href\":        {},\n\t\t\"src\":         {},\n\t\t\"action\":      {},\n\t\t\"method\":      {},\n\t\t\"placeholder\": {},\n\t}\n\n\tvar stableAttrs []string\n\tfor key := range attrs {\n\t\tif _, exists := stableKeys[key]; exists {\n\t\t\tstableAttrs = append(stableAttrs, key)\n\t\t}\n\t}\n\n\t// Sort the attributes to ensure consistent order for hashing.\n\tsort.Strings(stableAttrs)\n\n\treturn stableAttrs\n}\n\ntype EventListener struct {\n\tElement  *HTMLElement `json:\"element,omitempty\"`\n\tType     string       `json:\"type,omitempty\"`\n\tListener string       `json:\"listener,omitempty\"`\n}\n\n// NavigationType represents the type of navigation\ntype NavigationType string\n\nconst (\n\t// NavigationTypeForm represents a form navigation\n\tNavigationTypeForm NavigationType = \"form\"\n\t// NavigationTypeButton represents a button navigation\n\tNavigationTypeButton NavigationType = \"button\"\n\t// NavigationTypeLink represents a link navigation\n\tNavigationTypeLink NavigationType = \"link\"\n\t// NavigationTypeEventListener represents an event listener navigation\n\tNavigationTypeEventListener NavigationType = \"eventlistener\"\n)\n"
  },
  {
    "path": "pkg/engine/hybrid/crawl.go",
    "content": "package hybrid\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/proto\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/common\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nfunc (c *Crawler) navigateRequest(s *common.CrawlSession, request *navigation.Request) (*navigation.Response, error) {\n\tdepth := request.Depth + 1\n\tresponse := &navigation.Response{\n\t\tDepth:        depth,\n\t\tRootHostname: s.Hostname,\n\t}\n\n\tpage, err := s.Browser.Page(proto.TargetCreateTarget{})\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"hybrid: could not create target\")\n\t}\n\tdefer func() {\n\t\tif err := page.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing page: %v\\n\", err)\n\t\t}\n\t}()\n\tc.addHeadersToPage(page)\n\n\tpageRouter := NewHijack(page)\n\tpageRouter.SetPattern(&proto.FetchRequestPattern{\n\t\tURLPattern:   \"*\",\n\t\tRequestStage: proto.FetchRequestStageResponse,\n\t})\n\n\txhrRequests := []navigation.Request{}\n\tgo pageRouter.Start(func(e *proto.FetchRequestPaused) error {\n\t\tURL, err := urlutil.Parse(e.Request.URL)\n\t\tif err != nil {\n\t\t\treturn errkit.Wrap(err, \"hybrid: could not parse URL\")\n\t\t}\n\t\tbody, _ := FetchGetResponseBody(page, e)\n\t\theaders := make(map[string][]string)\n\t\tfor _, h := range e.ResponseHeaders {\n\t\t\theaders[h.Name] = []string{h.Value}\n\t\t}\n\t\tvar (\n\t\t\tstatusCode     int\n\t\t\tstatucCodeText string\n\t\t)\n\t\tif e.ResponseStatusCode != nil {\n\t\t\tstatusCode = *e.ResponseStatusCode\n\t\t}\n\t\tif e.ResponseStatusText != \"\" {\n\t\t\tstatucCodeText = e.ResponseStatusText\n\t\t} else {\n\t\t\tstatucCodeText = http.StatusText(statusCode)\n\t\t}\n\t\thttpreq, err := http.NewRequest(e.Request.Method, URL.String(), strings.NewReader(e.Request.PostData))\n\t\tif err != nil {\n\t\t\treturn errkit.Wrap(err, \"hybrid: could not new request\")\n\t\t}\n\t\t// Note: headers are originally sent using `c.addHeadersToPage` below changes are done so that\n\t\t// headers are reflected in request dump\n\t\t// Headers, CustomHeaders, and Cookies are present in e.Request.Headers. We need to consider all of them and not only CustomHeaders\n\t\t// Otherwise, we will miss headers and output will be inconsistent\n\t\tif httpreq != nil {\n\t\t\tfor k, v := range e.Request.Headers {\n\t\t\t\thttpreq.Header.Set(k, v.String())\n\t\t\t}\n\t\t}\n\n\t\thttpresp := &http.Response{\n\t\t\tProto:         \"HTTP/1.1\",\n\t\t\tProtoMajor:    1,\n\t\t\tProtoMinor:    1,\n\t\t\tStatusCode:    statusCode,\n\t\t\tStatus:        statucCodeText,\n\t\t\tHeader:        headers,\n\t\t\tBody:          io.NopCloser(bytes.NewReader(body)),\n\t\t\tRequest:       httpreq,\n\t\t\tContentLength: int64(len(body)),\n\t\t}\n\n\t\tvar rawBytesRequest, rawBytesResponse []byte\n\t\tif r, err := retryablehttp.FromRequest(httpreq); err == nil {\n\t\t\trawBytesRequest, _ = r.Dump()\n\t\t} else {\n\t\t\trawBytesRequest, _ = httputil.DumpRequestOut(httpreq, true)\n\t\t}\n\t\trawBytesResponse, _ = httputil.DumpResponse(httpresp, true)\n\n\t\tbodyReader, _ := goquery.NewDocumentFromReader(bytes.NewReader(body))\n\t\tvar technologies map[string]interface{}\n\t\tif c.Options.Wappalyzer != nil {\n\t\t\tfingerprints := c.Options.Wappalyzer.Fingerprint(headers, body)\n\t\t\ttechnologies = make(map[string]interface{}, len(fingerprints))\n\t\t\tfor k := range fingerprints {\n\t\t\t\ttechnologies[k] = struct{}{}\n\t\t\t}\n\t\t}\n\t\tresp := &navigation.Response{\n\t\t\tResp:          httpresp,\n\t\t\tBody:          string(body),\n\t\t\tReader:        bodyReader,\n\t\t\tDepth:         depth,\n\t\t\tRootHostname:  s.Hostname,\n\t\t\tTechnologies:  mapsutil.GetKeys(technologies),\n\t\t\tStatusCode:    statusCode,\n\t\t\tHeaders:       utils.FlattenHeaders(headers),\n\t\t\tRaw:           string(rawBytesResponse),\n\t\t\tContentLength: httpresp.ContentLength,\n\t\t\tKnowledgeBase: c.Options.ClassifyPage(string(body)),\n\t\t}\n\t\tresponse.ContentLength = resp.ContentLength\n\n\t\trequestHeaders := make(map[string][]string)\n\t\tfor name, value := range e.Request.Headers {\n\t\t\trequestHeaders[name] = []string{value.Str()}\n\t\t}\n\n\t\tshouldCapture := func(xhrExtraction bool) bool {\n\t\t\tresourceTypes := []proto.NetworkResourceType{\n\t\t\t\tproto.NetworkResourceTypeXHR,\n\t\t\t\tproto.NetworkResourceTypeFetch,\n\t\t\t\tproto.NetworkResourceTypeScript,\n\t\t\t}\n\n\t\t\treturn xhrExtraction && slices.Contains(resourceTypes, e.ResourceType)\n\t\t}\n\n\t\tif shouldCapture(c.Options.Options.XhrExtraction) {\n\t\t\tnetworkReq := navigation.Request{\n\t\t\t\tURL:    httpreq.URL.String(),\n\t\t\t\tMethod: httpreq.Method,\n\t\t\t\tBody:   e.Request.PostData,\n\t\t\t}\n\t\t\tif len(httpreq.Header) > 0 {\n\t\t\t\tnetworkReq.Headers = utils.FlattenHeaders(httpreq.Header)\n\t\t\t} else {\n\t\t\t\tnetworkReq.Headers = utils.FlattenHeaders(requestHeaders)\n\t\t\t}\n\t\t\txhrRequests = append(xhrRequests, networkReq)\n\t\t}\n\n\t\t// trim trailing /\n\t\tnormalizedheadlessURL := strings.TrimSuffix(e.Request.URL, \"/\")\n\t\tmatchOriginalURL := stringsutil.EqualFoldAny(request.URL, e.Request.URL, normalizedheadlessURL)\n\t\tif matchOriginalURL {\n\t\t\trequest.Raw = string(rawBytesRequest)\n\t\t\tresponse = resp\n\t\t}\n\n\t\t// process the raw response\n\t\tnavigationRequests := c.Options.Parser.ParseResponse(resp)\n\t\tc.Enqueue(s.Queue, navigationRequests...)\n\n\t\t// do not continue following the request if it's a redirect and redirects are disabled\n\t\tif c.Options.Options.DisableRedirects && resp.IsRedirect() {\n\t\t\treturn nil\n\t\t}\n\t\treturn FetchContinueRequest(page, e)\n\t})() //nolint\n\tdefer func() {\n\t\tif err := pageRouter.Stop(); err != nil {\n\t\t\tgologger.Warning().Msgf(\"%s\\n\", err)\n\t\t}\n\t}()\n\n\ttimeout := time.Duration(c.Options.Options.Timeout) * time.Second\n\tpage = page.Timeout(timeout)\n\n\tnavigatedURLs := sliceutil.NewSyncSlice[string]()\n\tnavigatedURLs.Append(request.URL)\n\n\tpageCtx, cancelPageEvents := page.WithCancel()\n\tdefer cancelPageEvents()\n\n\twaitFrameEvents := pageCtx.EachEvent(func(e *proto.PageFrameNavigated) {\n\t\tif e.Frame.ParentID == \"\" {\n\t\t\tframeURL := e.Frame.URL\n\t\t\tif frameURL != \"\" && frameURL != request.URL {\n\t\t\t\tnavigatedURLs.Append(frameURL)\n\t\t\t}\n\t\t}\n\t})\n\tgo waitFrameEvents()\n\n\t// wait the page to be fully loaded and becoming idle\n\twaitNavigation := page.WaitNavigation(proto.PageLifecycleEventNameFirstMeaningfulPaint)\n\n\terr = page.Navigate(request.URL)\n\tif err != nil {\n\t\tif c.Options.Options.DisableRedirects && response.IsRedirect() {\n\t\t\treturn response, nil\n\t\t}\n\t\treturn nil, errkit.Wrap(err, \"hybrid: could not navigate target\")\n\t}\n\n\twaitNavigation()\n\n\t// Wait the page to be stable a duration\n\ttimeStable := time.Duration(c.Options.Options.TimeStable) * time.Second\n\n\tif timeout < timeStable {\n\t\tgologger.Warning().Msgf(\"timeout is less than time stable, setting time stable to half of timeout to avoid timeout\\n\")\n\t\ttimeStable = timeout / 2\n\t\tgologger.Warning().Msgf(\"setting time stable to %s\\n\", timeStable)\n\t}\n\n\tif err := page.WaitStable(timeStable); err != nil {\n\t\tgologger.Warning().Msgf(\"could not wait for page to be stable: %s\\n\", err)\n\t}\n\n\t// simulate clicks on links with onclick handlers to discover JS redirects\n\ttime.Sleep(200 * time.Millisecond)\n\n\tclickableLinks, err := page.Elements(\"a[onclick]\")\n\tif err == nil && len(clickableLinks) > 0 {\n\t\tmaxLinks := c.Options.Options.MaxOnclickLinks\n\t\tlinksToProcess := len(clickableLinks)\n\t\tif linksToProcess > maxLinks {\n\t\t\tlinksToProcess = maxLinks\n\t\t}\n\n\t\tgologger.Debug().Msgf(\"Found %d clickable links with onclick handlers, processing %d\", len(clickableLinks), linksToProcess)\n\n\t\tfor idx := 0; idx < linksToProcess; idx++ {\n\t\t\tlink := clickableLinks[idx]\n\t\t\tbeforeURL, err := page.Info()\n\t\t\tif err != nil {\n\t\t\t\tgologger.Error().Msgf(\"Could not get page info: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbeforeURLStr := \"\"\n\t\t\tif beforeURL != nil {\n\t\t\t\tbeforeURLStr = beforeURL.URL\n\t\t\t}\n\n\t\t\t// try to click the link using rod's Click method\n\t\t\tclickErr := link.Click(proto.InputMouseButtonLeft, 1)\n\t\t\tif clickErr != nil {\n\t\t\t\tgologger.Debug().Msgf(\"Could not click link %d: %v\", idx, clickErr)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tgologger.Debug().Msgf(\"Clicked onclick link [%d] at URL: %s\", idx, beforeURLStr)\n\n\t\t\ttime.Sleep(1 * time.Second)\n\n\t\t\t// check if URL changed (indicates redirect occurred)\n\t\t\tcurrentURL, _ := page.Info()\n\t\t\tif currentURL != nil && currentURL.URL != beforeURLStr {\n\t\t\t\tgologger.Debug().Msgf(\"detected navigation to: %s\", currentURL.URL)\n\t\t\t\tnavigatedURLs.Append(currentURL.URL)\n\n\t\t\t\tif navErr := page.Navigate(request.URL); navErr != nil {\n\t\t\t\t\tgologger.Warning().Msgf(\"Failed to navigate back to %s after onclick redirect: %v\", request.URL, navErr)\n\t\t\t\t\tif reloadErr := page.Reload(); reloadErr != nil {\n\t\t\t\t\t\tgologger.Error().Msgf(\"Failed to reload page after navigation error: %v\", reloadErr)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar getDocumentDepth = int(-1)\n\tgetDocument := &proto.DOMGetDocument{Depth: &getDocumentDepth, Pierce: true}\n\tresult, err := getDocument.Call(page)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"hybrid: could not get dom\")\n\t}\n\tvar builder strings.Builder\n\ttraverseDOMNode(result.Root, &builder)\n\n\tbody, err := page.HTML()\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"hybrid: could not get html\")\n\t}\n\n\tparsed, err := urlutil.Parse(request.URL)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"hybrid: url could not be parsed\")\n\t}\n\n\tif response == nil || response.Resp == nil {\n\t\t// err is guaranteed to be nil, due to previous checks.\n\t\treturn nil, errors.New(\"hybrid: response is nil\")\n\t}\n\tresponse.Resp.Request.URL = parsed.URL\n\n\t// Create a copy of intrapolated shadow DOM elements and parse them separately\n\tresponseCopy := *response\n\tresponseCopy.Body = builder.String()\n\n\tresponseCopy.Reader, _ = goquery.NewDocumentFromReader(strings.NewReader(responseCopy.Body))\n\tif responseCopy.Reader != nil {\n\t\tnavigationRequests := c.Options.Parser.ParseResponse(&responseCopy)\n\t\tc.Enqueue(s.Queue, navigationRequests...)\n\t}\n\n\tresponse.Body = body\n\tif response.Reader != nil {\n\t\tresponse.Reader.Url, _ = url.Parse(request.URL)\n\t\tif c.Options.Options.FormExtraction {\n\t\t\tresponse.Forms = append(response.Forms, utils.ParseFormFields(response.Reader)...)\n\t\t}\n\t}\n\n\tresponse.Reader, err = goquery.NewDocumentFromReader(strings.NewReader(response.Body))\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"hybrid: could not parse html\")\n\t}\n\n\tresponse.XhrRequests = xhrRequests\n\n\t// enqueue JS-triggered navigation URLs that were detected\n\tnavigatedURLs.Each(func(i int, navURL string) error {\n\t\tif navURL != request.URL {\n\t\t\tparsed, err := urlutil.Parse(navURL)\n\t\t\tif err == nil {\n\t\t\t\tnavReq := &navigation.Request{\n\t\t\t\t\tURL:          parsed.String(),\n\t\t\t\t\tDepth:        depth,\n\t\t\t\t\tRootHostname: s.Hostname,\n\t\t\t\t}\n\t\t\t\tc.Enqueue(s.Queue, navReq)\n\t\t\t\tgologger.Debug().Msgf(\"enqueued JS navigation: %s\", navURL)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n\n\treturn response, nil\n}\n\nfunc (c *Crawler) addHeadersToPage(page *rod.Page) {\n\tif len(c.Headers) == 0 {\n\t\treturn\n\t}\n\n\tvar arr []string\n\n\tfor k, v := range c.Headers {\n\t\tswitch {\n\t\tcase stringsutil.EqualFoldAny(k, \"User-Agent\"):\n\t\t\tuserAgentParams := &proto.NetworkSetUserAgentOverride{\n\t\t\t\tUserAgent: v,\n\t\t\t}\n\t\t\tif err := page.SetUserAgent(userAgentParams); err != nil {\n\t\t\t\tgologger.Error().Msgf(\"headless: could not set user agent: %v\", err)\n\t\t\t}\n\t\tdefault:\n\t\t\tarr = append(arr, k, v)\n\t\t}\n\t}\n\n\tif len(arr) > 0 {\n\t\t_, err := page.SetExtraHeaders(arr)\n\t\tif err != nil {\n\t\t\tgologger.Error().Msgf(\"headless: could not set extra headers: %v\", err)\n\t\t}\n\t}\n}\n\n// traverseDOMNode performs traversal of node completely building a pseudo-HTML\n// from it including the Shadow DOM, Pseudo elements and other children.\n//\n// TODO: Remove this method when we implement human-like browser navigation\n// which will anyway use browser APIs to find elements instead of goquery\n// where they will have shadow DOM information.\nfunc traverseDOMNode(node *proto.DOMNode, builder *strings.Builder) {\n\tbuildDOMFromNode(node, builder)\n\tif node.TemplateContent != nil {\n\t\ttraverseDOMNode(node.TemplateContent, builder)\n\t}\n\tif node.ContentDocument != nil {\n\t\ttraverseDOMNode(node.ContentDocument, builder)\n\t}\n\tfor _, children := range node.Children {\n\t\ttraverseDOMNode(children, builder)\n\t}\n\tfor _, shadow := range node.ShadowRoots {\n\t\ttraverseDOMNode(shadow, builder)\n\t}\n\tfor _, pseudo := range node.PseudoElements {\n\t\ttraverseDOMNode(pseudo, builder)\n\t}\n}\n\nconst (\n\telementNode = 1\n)\n\nvar knownElements = map[string]struct{}{\n\t\"a\": {}, \"applet\": {}, \"area\": {}, \"audio\": {}, \"base\": {}, \"blockquote\": {}, \"body\": {}, \"button\": {}, \"embed\": {}, \"form\": {}, \"frame\": {}, \"html\": {}, \"iframe\": {}, \"img\": {}, \"import\": {}, \"input\": {}, \"isindex\": {}, \"link\": {}, \"meta\": {}, \"object\": {}, \"script\": {}, \"svg\": {}, \"table\": {}, \"video\": {},\n}\n\nfunc buildDOMFromNode(node *proto.DOMNode, builder *strings.Builder) {\n\tif node.NodeType != elementNode {\n\t\treturn\n\t}\n\tif _, ok := knownElements[node.LocalName]; !ok {\n\t\treturn\n\t}\n\tbuilder.WriteRune('<')\n\tbuilder.WriteString(node.LocalName)\n\tbuilder.WriteRune(' ')\n\tif len(node.Attributes) > 0 {\n\t\tfor i := 0; i < len(node.Attributes); i = i + 2 {\n\t\t\tbuilder.WriteString(node.Attributes[i])\n\t\t\tbuilder.WriteRune('=')\n\t\t\tbuilder.WriteString(\"\\\"\")\n\t\t\tbuilder.WriteString(node.Attributes[i+1])\n\t\t\tbuilder.WriteString(\"\\\"\")\n\t\t\tbuilder.WriteRune(' ')\n\t\t}\n\t}\n\tbuilder.WriteRune('>')\n\tbuilder.WriteString(\"</\")\n\tbuilder.WriteString(node.LocalName)\n\tbuilder.WriteRune('>')\n}\n"
  },
  {
    "path": "pkg/engine/hybrid/doc.go",
    "content": "// Package hybrid implements the functionality for a hybrid-headless crawler.\n// It uses both headless browser and net/http for making requests, and goquery for processing rawand dom-rendered web page HTML.\npackage hybrid\n"
  },
  {
    "path": "pkg/engine/hybrid/hijack.go",
    "content": "package hybrid\n\nimport (\n\t\"encoding/base64\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/proto\"\n)\n\n// NewHijack create hijack from page.\nfunc NewHijack(page *rod.Page) *Hijack {\n\treturn &Hijack{\n\t\tpage:    page,\n\t\tdisable: &proto.FetchDisable{},\n\t}\n}\n\n// HijackHandler type\ntype HijackHandler = func(e *proto.FetchRequestPaused) error\n\n// Hijack is a hijack handler\ntype Hijack struct {\n\tpage    *rod.Page\n\tenable  *proto.FetchEnable\n\tdisable *proto.FetchDisable\n\tcancel  func()\n}\n\n// SetPattern set pattern directly\nfunc (h *Hijack) SetPattern(pattern *proto.FetchRequestPattern) {\n\th.enable = &proto.FetchEnable{\n\t\tPatterns: []*proto.FetchRequestPattern{pattern},\n\t}\n}\n\n// Start hijack.\nfunc (h *Hijack) Start(handler HijackHandler) func() error {\n\tif h.enable == nil {\n\t\tpanic(\"hijack pattern not set\")\n\t}\n\n\tp, cancel := h.page.WithCancel()\n\th.cancel = cancel\n\n\terr := h.enable.Call(p)\n\tif err != nil {\n\t\treturn func() error { return err }\n\t}\n\n\twait := p.EachEvent(func(e *proto.FetchRequestPaused) {\n\t\tif handler != nil {\n\t\t\terr = handler(e)\n\t\t}\n\t})\n\n\treturn func() error {\n\t\twait()\n\t\treturn err\n\t}\n}\n\n// Stop\nfunc (h *Hijack) Stop() error {\n\tif h.cancel != nil {\n\t\th.cancel()\n\t}\n\treturn h.disable.Call(h.page)\n}\n\n// FetchGetResponseBody get request body.\nfunc FetchGetResponseBody(page *rod.Page, e *proto.FetchRequestPaused) ([]byte, error) {\n\tm := proto.FetchGetResponseBody{\n\t\tRequestID: e.RequestID,\n\t}\n\tr, err := m.Call(page)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !r.Base64Encoded {\n\t\treturn []byte(r.Body), nil\n\t}\n\n\tbs, err := base64.StdEncoding.DecodeString(r.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bs, nil\n}\n\n// FetchContinueRequest continue request\nfunc FetchContinueRequest(page *rod.Page, e *proto.FetchRequestPaused) error {\n\tm := proto.FetchContinueRequest{\n\t\tRequestID: e.RequestID,\n\t}\n\treturn m.Call(page)\n}\n"
  },
  {
    "path": "pkg/engine/hybrid/hybrid.go",
    "content": "package hybrid\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/go-rod/rod\"\n\t\"github.com/go-rod/rod/lib/launcher\"\n\t\"github.com/go-rod/rod/lib/launcher/flags\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/common\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// Crawler is a standard crawler instance\ntype Crawler struct {\n\t*common.Shared\n\n\tbrowser *rod.Browser\n\t// TODO: Remove the Chrome PID kill code in favor of using Leakless(true).\n\t// This change will be made if there are no complaints about zombie Chrome processes.\n\t// References:\n\t// https://github.com/projectdiscovery/katana/issues/632\n\t// https://github.com/projectdiscovery/httpx/issues/1425\n\t// previousPIDs map[int32]struct{} // track already running PIDs\n\ttempDir string\n}\n\n// New returns a new standard crawler instance\nfunc New(options *types.CrawlerOptions) (*Crawler, error) {\n\tvar dataStore string\n\tvar err error\n\tif options.Options.ChromeDataDir != \"\" {\n\t\tdataStore = options.Options.ChromeDataDir\n\t} else {\n\t\tdataStore, err = os.MkdirTemp(\"\", \"katana-*\")\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"hybrid: could not create temporary directory\")\n\t\t}\n\t}\n\n\t// previousPIDs := processutil.FindProcesses(processutil.IsChromeProcess)\n\n\tvar launcherURL string\n\tvar chromeLauncher *launcher.Launcher\n\n\tif options.Options.ChromeWSUrl != \"\" {\n\t\tlauncherURL = options.Options.ChromeWSUrl\n\t} else {\n\t\t// create new chrome launcher instance\n\t\tchromeLauncher, err = buildChromeLauncher(options, dataStore)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// launch chrome headless process\n\t\tlauncherURL, err = chromeLauncher.Launch()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tbrowser := rod.New().ControlURL(launcherURL)\n\tif browserErr := browser.Connect(); browserErr != nil {\n\t\treturn nil, errkit.Wrap(browserErr, fmt.Sprintf(\"hybrid: failed to connect to chrome instance at %s\", launcherURL))\n\t}\n\n\t// create a new browser instance (default to incognito mode)\n\tif !options.Options.HeadlessNoIncognito {\n\t\tincognito, err := browser.Incognito()\n\t\tif err != nil {\n\t\t\tif chromeLauncher != nil {\n\t\t\t\tchromeLauncher.Kill()\n\t\t\t}\n\t\t\treturn nil, errkit.Wrap(err, \"hybrid: failed to create incognito browser\")\n\t\t}\n\t\tbrowser = incognito\n\t}\n\n\tshared, err := common.NewShared(options)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"hybrid\")\n\t}\n\n\tcrawler := &Crawler{\n\t\tShared:  shared,\n\t\tbrowser: browser,\n\t\t// previousPIDs: previousPIDs,\n\t\ttempDir: dataStore,\n\t}\n\n\treturn crawler, nil\n}\n\n// Close closes the crawler process\nfunc (c *Crawler) Close() error {\n\tif c.Options.Options.ChromeDataDir == \"\" {\n\t\tif err := os.RemoveAll(c.tempDir); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// processutil.CloseProcesses(processutil.IsChromeProcess, c.previousPIDs)\n\treturn nil\n}\n\n// Crawl crawls a URL with the specified options\nfunc (c *Crawler) Crawl(rootURL string) error {\n\tcrawlSession, err := c.NewCrawlSessionWithURL(rootURL)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"hybrid\")\n\t}\n\tcrawlSession.Browser = c.browser\n\n\tdefer crawlSession.CancelFunc()\n\n\tgologger.Info().Msgf(\"Started headless crawling for => %v\", rootURL)\n\tif err := c.Do(crawlSession, c.navigateRequest); err != nil {\n\t\treturn errkit.Wrap(err, \"hybrid\")\n\t}\n\treturn nil\n}\n\n// Do executes the crawling loop with browser-safe concurrency.\n// Unlike the base implementation, this uses sequential processing (concurrency=1)\n// because Chrome DevTools Protocol operations cannot safely run concurrently\n// on the same browser instance. Multiple concurrent page operations cause\n// race conditions, navigation conflicts, and network interception issues.\nfunc (c *Crawler) Do(crawlSession *common.CrawlSession, doRequest common.DoRequestFunc) error {\n\tfor item := range crawlSession.Queue.Pop() {\n\t\tif ctxErr := crawlSession.Ctx.Err(); ctxErr != nil {\n\t\t\treturn ctxErr\n\t\t}\n\n\t\treq, ok := item.(*navigation.Request)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif !utils.IsURL(req.URL) {\n\t\t\tif c.Options.Options.OnSkipURL != nil {\n\t\t\t\tc.Options.Options.OnSkipURL(req.URL)\n\t\t\t}\n\t\t\tgologger.Debug().Msgf(\"`%v` not a url. skipping\", req.URL)\n\t\t\tcontinue\n\t\t}\n\n\t\tif !c.Options.ValidatePath(req.URL) {\n\t\t\tgologger.Debug().Msgf(\"`%v` filtered path. skipping\", req.URL)\n\t\t\tcontinue\n\t\t}\n\n\t\tinScope, scopeErr := c.Options.ValidateScope(req.URL, crawlSession.Hostname)\n\t\tif scopeErr != nil {\n\t\t\tgologger.Debug().Msgf(\"Error validating scope for `%v`: %v. skipping\", req.URL, scopeErr)\n\t\t\tcontinue\n\t\t}\n\t\tif !req.SkipValidation && !inScope {\n\t\t\tgologger.Debug().Msgf(\"`%v` not in scope. skipping\", req.URL)\n\t\t\tcontinue\n\t\t}\n\n\t\tc.Options.RateLimit.Take()\n\n\t\tif c.Options.Options.Delay > 0 {\n\t\t\ttime.Sleep(time.Duration(c.Options.Options.Delay) * time.Second)\n\t\t}\n\n\t\tresp, err := doRequest(crawlSession, req)\n\n\t\tif inScope {\n\t\t\tc.Output(req, resp, err)\n\t\t}\n\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"Could not request seed URL %s: %s\\n\", req.URL, err)\n\t\t\toutputError := &output.Error{\n\t\t\t\tTimestamp: time.Now(),\n\t\t\t\tEndpoint:  req.RequestURL(),\n\t\t\t\tSource:    req.Source,\n\t\t\t\tError:     err.Error(),\n\t\t\t}\n\t\t\t_ = c.Options.OutputWriter.WriteErr(outputError)\n\t\t\tcontinue\n\t\t}\n\t\tif resp == nil || resp.Resp == nil || resp.Reader == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif c.Options.Options.DisableRedirects && resp.IsRedirect() {\n\t\t\tcontinue\n\t\t}\n\n\t\tnavigationRequests := c.Options.Parser.ParseResponse(resp)\n\t\tc.Enqueue(crawlSession.Queue, navigationRequests...)\n\t}\n\treturn nil\n}\n\n// buildChromeLauncher builds a new chrome launcher instance\nfunc buildChromeLauncher(options *types.CrawlerOptions, dataStore string) (*launcher.Launcher, error) {\n\tchromeLauncher := launcher.New().\n\t\tLeakless(true).\n\t\tSet(\"disable-gpu\", \"true\").\n\t\tSet(\"ignore-certificate-errors\", \"true\").\n\t\tSet(\"ignore-certificate-errors\", \"1\").\n\t\tSet(\"disable-crash-reporter\", \"true\").\n\t\tSet(\"disable-notifications\", \"true\").\n\t\tSet(\"hide-scrollbars\", \"true\").\n\t\tSet(\"window-size\", fmt.Sprintf(\"%d,%d\", 1080, 1920)).\n\t\tSet(\"mute-audio\", \"true\").\n\t\tDelete(\"use-mock-keychain\").\n\t\tUserDataDir(dataStore)\n\n\tif options.Options.UseInstalledChrome {\n\t\tif options.Options.SystemChromePath != \"\" {\n\t\t\tchromeLauncher.Bin(options.Options.SystemChromePath)\n\t\t} else {\n\t\t\tif chromePath, hasChrome := launcher.LookPath(); hasChrome {\n\t\t\t\tchromeLauncher.Bin(chromePath)\n\t\t\t} else {\n\t\t\t\treturn nil, errkit.New(\"hybrid: the chrome browser is not installed\")\n\t\t\t}\n\t\t}\n\t}\n\tif options.Options.SystemChromePath != \"\" {\n\t\tchromeLauncher.Bin(options.Options.SystemChromePath)\n\t}\n\n\tif options.Options.ShowBrowser {\n\t\tchromeLauncher = chromeLauncher.Headless(false)\n\t} else {\n\t\tchromeLauncher = chromeLauncher.Headless(true)\n\t}\n\n\tif options.Options.HeadlessNoSandbox {\n\t\tchromeLauncher.Set(\"no-sandbox\", \"true\")\n\t}\n\n\tif options.Options.Proxy != \"\" && options.Options.Headless {\n\t\tproxyURL, err := urlutil.Parse(options.Options.Proxy)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tchromeLauncher.Set(\"proxy-server\", proxyURL.String())\n\t}\n\n\tfor k, v := range options.Options.ParseHeadlessOptionalArguments() {\n\t\tchromeLauncher.Set(flags.Flag(k), v)\n\t}\n\n\treturn chromeLauncher, nil\n}\n"
  },
  {
    "path": "pkg/engine/parser/files/request.go",
    "content": "package files\n\nimport (\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n)\n\ntype visitFunc func(URL string) ([]*navigation.Request, error)\n\ntype KnownFiles struct {\n\tparsers    []visitFunc\n\thttpclient *retryablehttp.Client\n}\n\n// New returns a new known files parser instance\nfunc New(httpclient *retryablehttp.Client, files string) *KnownFiles {\n\tparser := &KnownFiles{\n\t\thttpclient: httpclient,\n\t}\n\tswitch files {\n\tcase \"robotstxt\":\n\t\tcrawler := &robotsTxtCrawler{httpclient: httpclient}\n\t\tparser.parsers = append(parser.parsers, crawler.Visit)\n\tcase \"sitemapxml\":\n\t\tcrawler := &sitemapXmlCrawler{httpclient: httpclient}\n\t\tparser.parsers = append(parser.parsers, crawler.Visit)\n\tdefault:\n\t\tcrawler := &robotsTxtCrawler{httpclient: httpclient}\n\t\tparser.parsers = append(parser.parsers, crawler.Visit)\n\t\tanother := &sitemapXmlCrawler{httpclient: httpclient}\n\t\tparser.parsers = append(parser.parsers, another.Visit)\n\t}\n\treturn parser\n}\n\n// Request requests all known files with visitors\nfunc (k *KnownFiles) Request(URL string) (navigationRequests []*navigation.Request, err error) {\n\tfor _, visitor := range k.parsers {\n\t\tnavRequests, err := visitor(URL)\n\t\tif err != nil {\n\t\t\treturn navigationRequests, err\n\t\t}\n\t\tnavigationRequests = append(navigationRequests, navRequests...)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/engine/parser/files/robotstxt.go",
    "content": "package files\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\ntype robotsTxtCrawler struct {\n\thttpclient *retryablehttp.Client\n}\n\n// Visit visits the provided URL with file crawlers\nfunc (r *robotsTxtCrawler) Visit(URL string) ([]*navigation.Request, error) {\n\tURL = strings.TrimSuffix(URL, \"/\")\n\trequestURL := fmt.Sprintf(\"%s/robots.txt\", URL)\n\treq, err := retryablehttp.NewRequest(http.MethodGet, requestURL, nil)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"robotscrawler: could not create request\")\n\t}\n\treq.Header.Set(\"User-Agent\", utils.WebUserAgent())\n\n\tresp, err := r.httpclient.Do(req)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"robotscrawler: could not do request\")\n\t}\n\tdefer func() {\n\t\tif err := resp.Body.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing response body: %v\\n\", err)\n\t\t}\n\t}()\n\n\treturn r.parseReader(resp.Body, resp)\n}\n\nfunc (r *robotsTxtCrawler) parseReader(reader io.Reader, resp *http.Response) (navigationRequests []*navigation.Request, err error) {\n\tscanner := bufio.NewScanner(reader)\n\tfor scanner.Scan() {\n\t\ttext := scanner.Text()\n\t\tsplitted := strings.SplitN(text, \": \", 2)\n\t\tif len(splitted) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tdirective := strings.ToLower(splitted[0])\n\t\tif strings.HasPrefix(directive, \"allow\") || strings.EqualFold(directive, \"disallow\") {\n\t\t\tnavResp := &navigation.Response{\n\t\t\t\tDepth:      2,\n\t\t\t\tResp:       resp,\n\t\t\t\tStatusCode: resp.StatusCode,\n\t\t\t\tHeaders:    utils.FlattenHeaders(resp.Header),\n\t\t\t}\n\t\t\tnavRequest := navigation.NewNavigationRequestURLFromResponse(strings.Trim(splitted[1], \" \"), resp.Request.URL.String(), \"file\", \"robotstxt\", navResp)\n\t\t\tnavigationRequests = append(navigationRequests, navRequest)\n\t\t}\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/engine/parser/files/robotstxt_test.go",
    "content": "package files\n\nimport (\n\t\"net/http\"\n\n\t\"strings\"\n\t\"testing\"\n\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRobotsTxtParseReader(t *testing.T) {\n\trequests := []string{}\n\tcrawler := &robotsTxtCrawler{}\n\n\tcontent := `User-agent: *\nDisallow: /test/misc/known-files/robots.txt.found\n\nUser-agent: *\nDisallow: /test/includes/\n\n# User-agent: Googlebot\n# Allow: /random/\n\nSitemap: https://example.com/sitemap.xml`\n\tparsed, err := urlutil.Parse(\"http://localhost/robots.txt\")\n\trequire.Nil(t, err)\n\tnavigationRequests, err := crawler.parseReader(strings.NewReader(content), &http.Response{Request: &http.Request{URL: parsed.URL}})\n\trequire.Nil(t, err)\n\n\tfor _, navReq := range navigationRequests {\n\t\trequests = append(requests, navReq.URL)\n\t}\n\trequire.ElementsMatch(t, requests, []string{\n\t\t\"http://localhost/test/includes/\",\n\t\t\"http://localhost/test/misc/known-files/robots.txt.found\",\n\t}, \"could not get correct elements\")\n}\n"
  },
  {
    "path": "pkg/engine/parser/files/sitemapxml.go",
    "content": "package files\n\nimport (\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\ntype sitemapXmlCrawler struct {\n\thttpclient *retryablehttp.Client\n}\n\n// Visit visits the provided URL with file crawlers\nfunc (r *sitemapXmlCrawler) Visit(URL string) (navigationRequests []*navigation.Request, err error) {\n\tURL = strings.TrimSuffix(URL, \"/\")\n\trequestURL := fmt.Sprintf(\"%s/sitemap.xml\", URL)\n\treq, err := retryablehttp.NewRequest(http.MethodGet, requestURL, nil)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"sitemapcrawler: could not create request\")\n\t}\n\treq.Header.Set(\"User-Agent\", utils.WebUserAgent())\n\n\tresp, err := r.httpclient.Do(req)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"sitemapcrawler: could not do request\")\n\t}\n\tdefer func() {\n\t\tif err := resp.Body.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing response body: %v\\n\", err)\n\t\t}\n\t}()\n\n\tnavigationRequests, err = r.parseReader(resp.Body, resp)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"sitemapcrawler: could not parse sitemap\")\n\t}\n\treturn\n}\n\ntype sitemapStruct struct {\n\tURLs    []parsedURL `xml:\"url\"`\n\tSitemap []parsedURL `xml:\"sitemap\"`\n}\n\ntype parsedURL struct {\n\tLoc string `xml:\"loc\"`\n}\n\nfunc (r *sitemapXmlCrawler) parseReader(reader io.Reader, resp *http.Response) (navigationRequests []*navigation.Request, err error) {\n\tsitemap := sitemapStruct{}\n\tif err := xml.NewDecoder(reader).Decode(&sitemap); err != nil {\n\t\treturn nil, errkit.Wrap(err, \"sitemapcrawler: could not decode xml\")\n\t}\n\tfor _, url := range sitemap.URLs {\n\t\tnavResp := &navigation.Response{\n\t\t\tDepth:      2,\n\t\t\tResp:       resp,\n\t\t\tStatusCode: resp.StatusCode,\n\t\t\tHeaders:    utils.FlattenHeaders(resp.Header),\n\t\t}\n\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(strings.Trim(url.Loc, \" \\t\\n\"), resp.Request.URL.String(), \"file\", \"sitemapxml\", navResp))\n\t}\n\tfor _, url := range sitemap.Sitemap {\n\t\tnavResp := &navigation.Response{\n\t\t\tDepth:      2,\n\t\t\tResp:       resp,\n\t\t\tStatusCode: resp.StatusCode,\n\t\t\tHeaders:    utils.FlattenHeaders(resp.Header),\n\t\t}\n\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(strings.Trim(url.Loc, \" \\t\\n\"), resp.Request.URL.String(), \"file\", \"sitemapxml\", navResp))\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/engine/parser/files/sitemapxml_test.go",
    "content": "package files\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSitemapXmlParseReader(t *testing.T) {\n\trequests := []string{}\n\tcrawler := &sitemapXmlCrawler{}\n\n\tcontent := `<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n<sitemap>\n  \t<loc>\n\t\thttp://security-crawl-maze.app/test/misc/known-files/sitemap.xml.found\n\t</loc>\n\t<lastmod>2019-06-19T12:00:00+00:00</lastmod>\n</sitemap>\n</sitemapindex>`\n\tparsed, err := urlutil.Parse(\"http://security-crawl-maze.app/sitemap.xml\")\n\trequire.Nil(t, err)\n\tnavigationRequests, err := crawler.parseReader(strings.NewReader(content), &http.Response{Request: &http.Request{URL: parsed.URL}})\n\trequire.Nil(t, err)\n\tfor _, navReq := range navigationRequests {\n\t\trequests = append(requests, navReq.URL)\n\t}\n\trequire.ElementsMatch(t, requests, []string{\n\t\t\"http://security-crawl-maze.app/test/misc/known-files/sitemap.xml.found\",\n\t}, \"could not get correct elements\")\n}\n"
  },
  {
    "path": "pkg/engine/parser/parser.go",
    "content": "package parser\n\nimport (\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"golang.org/x/net/html\"\n)\n\n// responseParserFunc is a function that parses the document returning\n// new navigation items or requests for the crawler.\ntype ResponseParserFunc func(resp *navigation.Response) []*navigation.Request\n\ntype Parser []responseParser\n\ntype responseParserType int\n\nconst (\n\theaderParser responseParserType = iota + 1\n\tbodyParser\n\tcontentParser\n)\n\ntype responseParser struct {\n\tparserType responseParserType\n\tparserFunc ResponseParserFunc\n}\n\nfunc NewResponseParser() *Parser {\n\treturn &Parser{\n\t\t// Header based parsers\n\t\t{headerParser, headerContentLocationParser},\n\t\t{headerParser, headerLinkParser},\n\t\t{headerParser, headerRefreshParser},\n\n\t\t// Body based parsers\n\t\t{bodyParser, bodyATagParser},\n\t\t{bodyParser, bodyLinkHrefTagParser},\n\t\t{bodyParser, bodyBackgroundTagParser},\n\t\t{bodyParser, bodyAudioTagParser},\n\t\t{bodyParser, bodyAppletTagParser},\n\t\t{bodyParser, bodyImgTagParser},\n\t\t{bodyParser, bodyObjectTagParser},\n\t\t{bodyParser, bodySvgTagParser},\n\t\t{bodyParser, bodyTableTagParser},\n\t\t{bodyParser, bodyVideoTagParser},\n\t\t{bodyParser, bodyButtonFormactionTagParser},\n\t\t{bodyParser, bodyBlockquoteCiteTagParser},\n\t\t{bodyParser, bodyFrameSrcTagParser},\n\t\t{bodyParser, bodyMapAreaPingTagParser},\n\t\t{bodyParser, bodyBaseHrefTagParser},\n\t\t{bodyParser, bodyImportImplementationTagParser},\n\t\t{bodyParser, bodyEmbedTagParser},\n\t\t{bodyParser, bodyFrameTagParser},\n\t\t{bodyParser, bodyIframeTagParser},\n\t\t{bodyParser, bodyInputSrcTagParser},\n\t\t{bodyParser, bodyIsindexActionTagParser},\n\t\t{bodyParser, bodyScriptSrcTagParser},\n\t\t{bodyParser, bodyMetaContentTagParser},\n\t\t{bodyParser, bodyHtmlManifestTagParser},\n\t\t{bodyParser, bodyHtmlDoctypeTagParser},\n\t\t{bodyParser, bodyHtmxAttrParser},\n\n\t\t// custom field regex parser\n\t\t{bodyParser, customFieldRegexParser},\n\t}\n}\n\n// parseResponse runs the response parsers on the navigation response\nfunc (p *Parser) ParseResponse(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tfor _, parser := range *p {\n\t\tswitch {\n\t\tcase parser.parserType == headerParser && resp.Resp != nil:\n\t\t\tnavigationRequests = appendFiltered(navigationRequests, parser.parserFunc(resp))\n\t\tcase parser.parserType == bodyParser && resp.Reader != nil:\n\t\t\tnavigationRequests = appendFiltered(navigationRequests, parser.parserFunc(resp))\n\t\tcase parser.parserType == contentParser && len(resp.Body) > 0:\n\t\t\tnavigationRequests = appendFiltered(navigationRequests, parser.parserFunc(resp))\n\t\t}\n\t}\n\treturn\n}\n\n// appendFiltered filters navigation requests and appends valid ones to the slice\nfunc appendFiltered(existing []*navigation.Request, new []*navigation.Request) []*navigation.Request {\n\tfor _, req := range new {\n\t\tif isValidNavigationRequest(req) {\n\t\t\texisting = append(existing, req)\n\t\t}\n\t}\n\treturn existing\n}\n\nfunc isValidNavigationRequest(req *navigation.Request) bool {\n\tif req == nil {\n\t\treturn false\n\t}\n\turl := strings.TrimSpace(req.URL)\n\tif url == \"\" {\n\t\treturn false\n\t}\n\tlc := strings.ToLower(url)\n\treturn !strings.HasPrefix(lc, \"data:\") &&\n\t\t!strings.HasPrefix(lc, \"mailto:\") &&\n\t\t!strings.HasPrefix(lc, \"javascript:\") &&\n\t\t!strings.HasPrefix(lc, \"vbscript:\")\n}\n\n// -------------------------------------------------------------------------\n// Begin Header based parsers\n// -------------------------------------------------------------------------\n\n// headerContentLocationParser parsers Content-Location header from response\nfunc headerContentLocationParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\theader := resp.Resp.Header.Get(\"Content-Location\")\n\tif header == \"\" {\n\t\treturn\n\t}\n\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(header, resp.Resp.Request.URL.String(), \"header\", \"content-location\", resp))\n\treturn\n}\n\n// headerLinkParser parsers Link header from response\nfunc headerLinkParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\theader := resp.Resp.Header.Get(\"Link\")\n\tif header == \"\" {\n\t\treturn\n\t}\n\tvalues := utils.ParseLinkTag(header)\n\tfor _, value := range values {\n\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(value, resp.Resp.Request.URL.String(), \"header\", \"link\", resp))\n\t}\n\treturn\n}\n\n// headerLocationParser parsers Location header from response\nfunc headerLocationParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\theader := resp.Resp.Header.Get(\"Location\")\n\tif header == \"\" {\n\t\treturn\n\t}\n\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(header, resp.Resp.Request.URL.String(), \"header\", \"location\", resp))\n\treturn\n}\n\n// headerRefreshParser parsers Refresh header from response\nfunc headerRefreshParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\theader := resp.Resp.Header.Get(\"Refresh\")\n\tif header == \"\" {\n\t\treturn\n\t}\n\tvalues := utils.ParseRefreshTag(header)\n\tif values == \"\" {\n\t\treturn\n\t}\n\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(values, resp.Resp.Request.URL.String(), \"header\", \"refresh\", resp))\n\treturn\n}\n\n// -------------------------------------------------------------------------\n// Begin Body based parsers\n// -------------------------------------------------------------------------\n\n// bodyATagParser parses A tag from response\nfunc bodyATagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"a\").Each(func(i int, item *goquery.Selection) {\n\t\thref, ok := item.Attr(\"href\")\n\t\tif ok && href != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(href, resp.Resp.Request.URL.String(), \"a\", \"href\", resp))\n\t\t}\n\t\tping, ok := item.Attr(\"ping\")\n\t\tif ok && ping != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(ping, resp.Resp.Request.URL.String(), \"a\", \"ping\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyLinkHrefTagParser parses link tag from response\nfunc bodyLinkHrefTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"link[href]\").Each(func(i int, item *goquery.Selection) {\n\t\thref, ok := item.Attr(\"href\")\n\t\tif ok && href != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(href, resp.Resp.Request.URL.String(), \"link\", \"href\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyEmbedTagParser parses Embed tag from response\nfunc bodyEmbedTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"embed[src]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"embed\", \"src\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyFrameTagParser parses frame tag from response\nfunc bodyFrameTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"frame[src]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"frame\", \"src\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyIframeTagParser parses iframe tag from response\nfunc bodyIframeTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"iframe\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"iframe\", \"src\", resp))\n\t\t}\n\t\tsrcDoc, ok := item.Attr(\"srcdoc\")\n\t\tif ok && srcDoc != \"\" {\n\t\t\tendpoints := utils.ExtractRelativeEndpoints(srcDoc)\n\t\t\tfor _, item := range endpoints {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), \"iframe\", \"srcdoc\", resp))\n\t\t\t}\n\t\t}\n\t})\n\treturn\n}\n\n// bodyInputSrcTagParser parses input image src tag from response\nfunc bodyInputSrcTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"input[type='image' i]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"input-image\", \"src\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyIsindexActionTagParser parses isindex action tag from response\nfunc bodyIsindexActionTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"isindex[action]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"action\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"isindex\", \"action\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyScriptSrcTagParser parses script src tag from response\nfunc bodyScriptSrcTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"script[src]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"script\", \"src\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyBackgroundTagParser parses body background tag from response\nfunc bodyBackgroundTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"body[background]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"background\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"body\", \"background\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyAudioTagParser parses body audio tag from response\nfunc bodyAudioTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"audio\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"audio\", \"src\", resp))\n\t\t}\n\t\titem.Find(\"source\").Each(func(i int, s *goquery.Selection) {\n\t\t\tsrc, ok := s.Attr(\"src\")\n\t\t\tif ok && src != \"\" {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"audio\", \"source\", resp))\n\t\t\t}\n\t\t\tsrcSet, ok := s.Attr(\"srcset\")\n\t\t\tif ok && srcSet != \"\" {\n\t\t\t\tfor _, value := range utils.ParseSRCSetTag(srcSet) {\n\t\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(value, resp.Resp.Request.URL.String(), \"audio\", \"sourcesrcset\", resp))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t})\n\treturn\n}\n\n// bodyAppletTagParser parses body applet tag from response\nfunc bodyAppletTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"applet\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"archive\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"applet\", \"archive\", resp))\n\t\t}\n\t\tsrcCodebase, ok := item.Attr(\"codebase\")\n\t\tif ok && srcCodebase != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcCodebase, resp.Resp.Request.URL.String(), \"applet\", \"codebase\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyImgTagParser parses Img tag from response\nfunc bodyImgTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"img\").Each(func(i int, item *goquery.Selection) {\n\t\tsrcDynsrc, ok := item.Attr(\"dynsrc\")\n\t\tif ok && srcDynsrc != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcDynsrc, resp.Resp.Request.URL.String(), \"img\", \"dynsrc\", resp))\n\t\t}\n\t\tsrcLongdesc, ok := item.Attr(\"longdesc\")\n\t\tif ok && srcLongdesc != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcLongdesc, resp.Resp.Request.URL.String(), \"img\", \"longdesc\", resp))\n\t\t}\n\t\tsrcLowsrc, ok := item.Attr(\"lowsrc\")\n\t\tif ok && srcLowsrc != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcLowsrc, resp.Resp.Request.URL.String(), \"img\", \"lowsrc\", resp))\n\t\t}\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" && src != \"#\" {\n\t\t\tif strings.HasPrefix(src, \"data:\") {\n\t\t\t\t// TODO: Add data:uri/data:image parsing\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"img\", \"src\", resp))\n\t\t}\n\t\tsrcSet, ok := item.Attr(\"srcset\")\n\t\tif ok && srcSet != \"\" {\n\t\t\tfor _, value := range utils.ParseSRCSetTag(srcSet) {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(value, resp.Resp.Request.URL.String(), \"img\", \"srcset\", resp))\n\t\t\t}\n\t\t}\n\t})\n\treturn\n}\n\n// bodyObjectTagParser parses object tag from response\nfunc bodyObjectTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"object\").Each(func(i int, item *goquery.Selection) {\n\t\tsrcData, ok := item.Attr(\"data\")\n\t\tif ok && srcData != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcData, resp.Resp.Request.URL.String(), \"src\", \"data\", resp))\n\t\t}\n\t\tsrcCodebase, ok := item.Attr(\"codebase\")\n\t\tif ok && srcCodebase != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcCodebase, resp.Resp.Request.URL.String(), \"src\", \"codebase\", resp))\n\t\t}\n\t\titem.Find(\"param\").Each(func(i int, s *goquery.Selection) {\n\t\t\tsrcValue, ok := s.Attr(\"value\")\n\t\t\tif ok && srcValue != \"\" {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcValue, resp.Resp.Request.URL.String(), \"src\", \"value\", resp))\n\t\t\t}\n\t\t})\n\t})\n\treturn\n}\n\n// bodySvgTagParser parses svg tag from response\nfunc bodySvgTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"svg\").Each(func(i int, item *goquery.Selection) {\n\t\titem.Find(\"image\").Each(func(i int, s *goquery.Selection) {\n\t\t\threfData, ok := s.Attr(\"href\")\n\t\t\tif ok && hrefData != \"\" {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(hrefData, resp.Resp.Request.URL.String(), \"svg\", \"image-href\", resp))\n\t\t\t}\n\t\t})\n\t\titem.Find(\"script\").Each(func(i int, s *goquery.Selection) {\n\t\t\threfData, ok := s.Attr(\"href\")\n\t\t\tif ok && hrefData != \"\" {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(hrefData, resp.Resp.Request.URL.String(), \"svg\", \"script-href\", resp))\n\t\t\t}\n\t\t})\n\t})\n\treturn\n}\n\n// bodyTableTagParser parses table tag from response\nfunc bodyTableTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"table\").Each(func(i int, item *goquery.Selection) {\n\t\tsrcData, ok := item.Attr(\"background\")\n\t\tif ok && srcData != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcData, resp.Resp.Request.URL.String(), \"table\", \"background\", resp))\n\t\t}\n\t\titem.Find(\"td\").Each(func(i int, s *goquery.Selection) {\n\t\t\tsrcValue, ok := s.Attr(\"background\")\n\t\t\tif ok && srcValue != \"\" {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcValue, resp.Resp.Request.URL.String(), \"table\", \"td-background\", resp))\n\t\t\t}\n\t\t})\n\t})\n\treturn\n}\n\n// bodyVideoTagParser parses video tag from response\nfunc bodyVideoTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"video\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"video\", \"src\", resp))\n\t\t}\n\t\tsrcData, ok := item.Attr(\"poster\")\n\t\tif ok && srcData != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcData, resp.Resp.Request.URL.String(), \"video\", \"poster\", resp))\n\t\t}\n\t\titem.Find(\"track\").Each(func(i int, s *goquery.Selection) {\n\t\t\tsrcValue, ok := s.Attr(\"src\")\n\t\t\tif ok && srcValue != \"\" {\n\t\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(srcValue, resp.Resp.Request.URL.String(), \"video\", \"track-src\", resp))\n\t\t\t}\n\t\t})\n\t})\n\treturn\n}\n\n// bodyBlockquoteCiteTagParser parses blockquote cite tag from response\nfunc bodyBlockquoteCiteTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"blockquote[cite]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"cite\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"blockquote\", \"cite\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyFrameSrcTagParser parses frame src tag from response\nfunc bodyFrameSrcTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"frame[src]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"src\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"frame\", \"src\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyMapAreaPingTagParser parses map area ping tag from response\nfunc bodyMapAreaPingTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"area[ping]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"ping\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"area\", \"ping\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyBaseHrefTagParser parses base href tag from response\nfunc bodyBaseHrefTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"base[href]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"href\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"base\", \"href\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyImportImplementationTagParser parses import implementation tag from response\nfunc bodyImportImplementationTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"import[implementation]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"implementation\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"import\", \"implementation\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyButtonFormactionTagParser parses button formaction tag from response\nfunc bodyButtonFormactionTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"button[formaction]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"formaction\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"button\", \"formaction\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyHtmlManifestTagParser parses body manifest tag from response\nfunc bodyHtmlManifestTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"html[manifest]\").Each(func(i int, item *goquery.Selection) {\n\t\tsrc, ok := item.Attr(\"manifest\")\n\t\tif ok && src != \"\" {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(src, resp.Resp.Request.URL.String(), \"html\", \"manifest\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// bodyHtmlDoctypeTagParser parses body doctype tag from response\nfunc bodyHtmlDoctypeTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tif len(resp.Reader.Nodes) < 1 || resp.Reader.Nodes[0].FirstChild == nil {\n\t\treturn\n\t}\n\tdocTypeNode := resp.Reader.Nodes[0].FirstChild\n\tif docTypeNode.Type != html.DoctypeNode {\n\t\treturn\n\t}\n\tif len(docTypeNode.Attr) == 0 || strings.ToLower(docTypeNode.Attr[0].Key) != \"system\" {\n\t\treturn\n\t}\n\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(docTypeNode.Attr[0].Val, resp.Resp.Request.URL.String(), \"html\", \"doctype\", resp))\n\treturn\n}\n\n// bodyFormTagParser parses forms from response\nfunc bodyFormTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"form\").Each(func(i int, item *goquery.Selection) {\n\t\thref, _ := item.Attr(\"action\")\n\t\tencType, ok := item.Attr(\"enctype\")\n\t\tif !ok || encType == \"\" {\n\t\t\tencType = \"application/x-www-form-urlencoded\"\n\t\t}\n\n\t\tmethod, _ := item.Attr(\"method\")\n\t\tif method == \"\" {\n\t\t\tmethod = \"GET\"\n\t\t}\n\t\tmethod = strings.ToUpper(method)\n\n\t\tactionURL := resp.AbsoluteURL(href)\n\t\tif actionURL == \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tparsed, err := urlutil.Parse(actionURL)\n\t\tif err != nil {\n\t\t\tgologger.Warning().Msgf(\"bodyFormTagParser :failed to parse url %v got %v\", actionURL, err)\n\t\t\treturn\n\t\t}\n\n\t\tisMultipartForm := strings.HasPrefix(encType, \"multipart/\")\n\n\t\tqueryValuesWriter := urlutil.NewOrderedParams()\n\t\tqueryValuesWriter.IncludeEquals = true\n\t\tvar sb strings.Builder\n\t\tvar multipartWriter *multipart.Writer\n\n\t\tif isMultipartForm {\n\t\t\tmultipartWriter = multipart.NewWriter(&sb)\n\t\t}\n\n\t\t// Get the form field suggestions for all elements in the form\n\t\tformFields := []interface{}{}\n\t\titem.Find(\"input, select, textarea\").Each(func(index int, item *goquery.Selection) {\n\t\t\tif len(item.Nodes) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tformFields = append(formFields, utils.ConvertGoquerySelectionToFormField(item))\n\t\t})\n\n\t\tdataMap := utils.FormFillSuggestions(formFields)\n\t\tdataMap.Iterate(func(key, value string) bool {\n\t\t\tif key == \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif isMultipartForm {\n\t\t\t\t_ = multipartWriter.WriteField(key, value)\n\t\t\t} else {\n\t\t\t\tqueryValuesWriter.Set(key, value)\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\n\t\t// Guess content-type\n\t\tvar contentType string\n\t\tif multipartWriter != nil {\n\t\t\t_ = multipartWriter.Close()\n\t\t\tcontentType = multipartWriter.FormDataContentType()\n\t\t} else {\n\t\t\tcontentType = encType\n\t\t}\n\n\t\treq := &navigation.Request{\n\t\t\tMethod:       method,\n\t\t\tURL:          actionURL,\n\t\t\tDepth:        resp.Depth,\n\t\t\tRootHostname: resp.RootHostname,\n\t\t\tTag:          \"form\",\n\t\t\tAttribute:    \"action\",\n\t\t\tSource:       resp.Resp.Request.URL.String(),\n\t\t}\n\t\tswitch method {\n\t\tcase \"GET\":\n\t\t\tparsed.Params.Merge(queryValuesWriter.Encode())\n\t\t\treq.URL = parsed.String()\n\t\tcase \"POST\":\n\t\t\tif multipartWriter != nil {\n\t\t\t\treq.Body = sb.String()\n\t\t\t} else {\n\t\t\t\treq.Body = queryValuesWriter.Encode()\n\t\t\t}\n\t\t\treq.Headers = make(map[string]string)\n\t\t\treq.Headers[\"Content-Type\"] = contentType\n\t\t}\n\t\tnavigationRequests = append(navigationRequests, req)\n\t})\n\treturn\n}\n\n// bodyMetaContentTagParser parses meta content tag from response\nfunc bodyMetaContentTagParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"meta\").Each(func(i int, item *goquery.Selection) {\n\t\theader, ok := item.Attr(\"content\")\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\textracted := utils.ExtractRelativeEndpoints(header)\n\t\tfor _, item := range extracted {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), \"meta\", \"refresh\", resp))\n\t\t}\n\t})\n\treturn\n}\n\nfunc bodyHtmxAttrParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\t// exclude hx-delete\n\tresp.Reader.Find(\"[hx-get],[hx-post],[hx-put],[hx-patch]\").Each(func(i int, item *goquery.Selection) {\n\t\treq := &navigation.Request{\n\t\t\tRootHostname: resp.RootHostname,\n\t\t\tDepth:        resp.Depth,\n\t\t\tSource:       resp.Resp.Request.URL.String(),\n\t\t\tTag:          \"htmx\",\n\t\t}\n\n\t\tif hxGet, ok := item.Attr(\"hx-get\"); ok && hxGet != \"\" {\n\t\t\treq.Method = http.MethodGet\n\t\t\treq.URL = resp.AbsoluteURL(hxGet)\n\t\t\treq.Attribute = \"hx-get\"\n\t\t\tnavigationRequests = append(navigationRequests, req)\n\t\t}\n\t\tif hxPost, ok := item.Attr((\"hx-post\")); ok && hxPost != \"\" {\n\t\t\treq.Method = http.MethodPost\n\t\t\treq.URL = resp.AbsoluteURL(hxPost)\n\t\t\treq.Attribute = \"hx-post\"\n\t\t\tnavigationRequests = append(navigationRequests, req)\n\t\t}\n\t\tif hxPut, ok := item.Attr((\"hx-put\")); ok && hxPut != \"\" {\n\t\t\treq.Method = http.MethodPut\n\t\t\treq.URL = resp.AbsoluteURL(hxPut)\n\t\t\treq.Attribute = \"hx-put\"\n\t\t\tnavigationRequests = append(navigationRequests, req)\n\t\t}\n\t\tif hxPatch, ok := item.Attr((\"hx-patch\")); ok && hxPatch != \"\" {\n\t\t\treq.Method = http.MethodPatch\n\t\t\treq.URL = resp.AbsoluteURL(hxPatch)\n\t\t\treq.Attribute = \"hx-patch\"\n\t\t\tnavigationRequests = append(navigationRequests, req)\n\t\t}\n\t})\n\treturn\n}\n\n// -------------------------------------------------------------------------\n// Begin JS Regex based parsers\n// -------------------------------------------------------------------------\n\n// scriptContentRegexParser parses script content endpoints from response\nfunc scriptContentRegexParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"script\").Each(func(i int, item *goquery.Selection) {\n\t\ttext := item.Text()\n\t\tif text == \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tendpoints := utils.ExtractRelativeEndpoints(text)\n\t\tfor _, item := range endpoints {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), \"script\", \"text\", resp))\n\t\t}\n\t})\n\treturn\n}\n\n// scriptJSFileRegexParser parses relative endpoints from js file pages\nfunc scriptJSFileRegexParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\t// Only process javascript file based on path or content type\n\t// CSS, JS are supported for relative endpoint extraction.\n\tcontentType := resp.Resp.Header.Get(\"Content-Type\")\n\tif !stringsutil.HasSuffixAny(resp.Resp.Request.URL.Path, \".js\", \".css\") && !strings.Contains(contentType, \"/javascript\") {\n\t\treturn\n\t}\n\n\tendpointsItems := utils.ExtractRelativeEndpoints(string(resp.Body))\n\tfor _, item := range endpointsItems {\n\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), \"js\", \"regex\", resp))\n\t}\n\treturn\n}\n\n// bodyScrapeEndpointsParser parses scraped URLs from HTML body\nfunc bodyScrapeEndpointsParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tendpoints := utils.ExtractBodyEndpoints(string(resp.Body))\n\tfor _, item := range endpoints {\n\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item, resp.Resp.Request.URL.String(), \"html\", \"regex\", resp))\n\t}\n\treturn\n}\n\n// customFieldRegexParser parses custom regex from HTML body and header\nfunc customFieldRegexParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tvar customField = make(map[string][]string)\n\tfor _, v := range output.CustomFieldsMap {\n\t\tresults := []string{}\n\t\tfor _, re := range v.CompileRegex {\n\t\t\tmatches := [][]string{}\n\n\t\t\t// read body\n\t\t\tif v.Part == output.Body.ToString() || v.Part == output.Response.ToString() {\n\t\t\t\tmatches = re.FindAllStringSubmatch(string(resp.Body), -1)\n\t\t\t}\n\n\t\t\t// read header\n\t\t\tif v.Part == output.Header.ToString() || v.Part == output.Response.ToString() {\n\t\t\t\tfor key, v := range resp.Resp.Header {\n\t\t\t\t\theader := key + \": \" + strings.Join(v, \"\\n\")\n\t\t\t\t\theaderMatches := re.FindAllStringSubmatch(header, -1)\n\t\t\t\t\tmatches = append(matches, headerMatches...)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor _, match := range matches {\n\t\t\t\tif len(match) < (v.Group + 1) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tmatchString := match[v.Group]\n\t\t\t\tresults = append(results, matchString)\n\t\t\t}\n\t\t}\n\t\tif len(results) > 0 {\n\t\t\tcustomField[v.GetName()] = results\n\t\t}\n\t}\n\tif len(customField) != 0 {\n\t\tnavigationRequests = append(navigationRequests, &navigation.Request{\n\t\t\tMethod:       \"GET\",\n\t\t\tURL:          resp.Resp.Request.URL.String(),\n\t\t\tDepth:        resp.Depth,\n\t\t\tCustomFields: customField,\n\t\t})\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/engine/parser/parser_generic.go",
    "content": "//go:build !(386 || windows)\n\npackage parser\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\ntype Options struct {\n\tAutomaticFormFill      bool\n\tScrapeJSLuiceResponses bool\n\tScrapeJSResponses      bool\n\tDisableRedirects       bool\n}\n\nfunc (p *Parser) InitWithOptions(options *Options) {\n\tif options.AutomaticFormFill {\n\t\t*p = append(*p, responseParser{bodyParser, bodyFormTagParser})\n\t}\n\tif options.ScrapeJSLuiceResponses {\n\t\t*p = append(*p, responseParser{bodyParser, scriptContentJsluiceParser})\n\t\t*p = append(*p, responseParser{contentParser, scriptJSFileJsluiceParser})\n\t}\n\tif options.ScrapeJSResponses {\n\t\t*p = append(*p, responseParser{bodyParser, scriptContentRegexParser})\n\t\t*p = append(*p, responseParser{contentParser, scriptJSFileRegexParser})\n\t\t*p = append(*p, responseParser{contentParser, bodyScrapeEndpointsParser})\n\t}\n\tif !options.DisableRedirects {\n\t\t*p = append(*p, responseParser{headerParser, headerLocationParser})\n\t}\n}\n\n// scriptContentJsluiceParser parses script content endpoints using jsluice from response\nfunc scriptContentJsluiceParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\tresp.Reader.Find(\"script\").Each(func(i int, item *goquery.Selection) {\n\t\ttext := item.Text()\n\t\tif text == \"\" {\n\t\t\treturn\n\t\t}\n\n\t\tendpointItems := utils.ExtractJsluiceEndpoints(text)\n\t\tfor _, item := range endpointItems {\n\t\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item.Endpoint, resp.Resp.Request.URL.String(), \"script\", fmt.Sprintf(\"jsluice-%s\", item.Type), resp))\n\t\t}\n\t})\n\treturn\n}\n\n// scriptJSFileJsluiceParser parses endpoints using jsluice from js file pages\nfunc scriptJSFileJsluiceParser(resp *navigation.Response) (navigationRequests []*navigation.Request) {\n\t// Only process javascript file based on path or content type\n\t// CSS, JS are supported for relative endpoint extraction.\n\tcontentType := resp.Resp.Header.Get(\"Content-Type\")\n\tif !stringsutil.HasSuffixAny(resp.Resp.Request.URL.Path, \".js\", \".css\") && !strings.Contains(contentType, \"/javascript\") {\n\t\treturn\n\t}\n\t// Skip common js libraries\n\tif utils.IsPathCommonJSLibraryFile(resp.Resp.Request.URL.Path) {\n\t\treturn\n\t}\n\n\tendpointsItems := utils.ExtractJsluiceEndpoints(string(resp.Body))\n\tfor _, item := range endpointsItems {\n\t\tnavigationRequests = append(navigationRequests, navigation.NewNavigationRequestURLFromResponse(item.Endpoint, resp.Resp.Request.URL.String(), \"js\", fmt.Sprintf(\"jsluice-%s\", item.Type), resp))\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/engine/parser/parser_nojs.go",
    "content": "//go:build windows || 386\n\npackage parser\n\ntype Options struct {\n\tAutomaticFormFill      bool\n\tScrapeJSLuiceResponses bool\n\tScrapeJSResponses      bool\n\tDisableRedirects       bool\n}\n\nfunc (p *Parser) InitWithOptions(options *Options) {\n\tif options.AutomaticFormFill {\n\t\t*p = append(*p, responseParser{bodyParser, bodyFormTagParser})\n\t}\n\tif options.ScrapeJSResponses {\n\t\t*p = append(*p, responseParser{bodyParser, scriptContentRegexParser})\n\t\t*p = append(*p, responseParser{contentParser, scriptJSFileRegexParser})\n\t\t*p = append(*p, responseParser{contentParser, bodyScrapeEndpointsParser})\n\t}\n\tif !options.DisableRedirects {\n\t\t*p = append(*p, responseParser{headerParser, headerLocationParser})\n\t}\n}\n"
  },
  {
    "path": "pkg/engine/parser/parser_test.go",
    "content": "package parser\n\nimport (\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestHeaderParsers(t *testing.T) {\n\tparsed, _ := urlutil.Parse(\"https://security-crawl-maze.app/headers/xyz/\")\n\n\tt.Run(\"content-location\", func(t *testing.T) {\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{\"Content-Location\": []string{\"/test/headers/content-location.found\"}}}}\n\t\tnavigationRequests := headerContentLocationParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/headers/content-location.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"link\", func(t *testing.T) {\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{\"Link\": []string{\"</test/headers/link.found>; rel=\\\"preload\\\"\"}}}}\n\t\tnavigationRequests := headerLinkParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/headers/link.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"location\", func(t *testing.T) {\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{\"Location\": []string{\"http://security-crawl-maze.app/test/headers/location.found\"}}}}\n\t\tnavigationRequests := headerLocationParser(resp)\n\t\trequire.Equal(t, \"http://security-crawl-maze.app/test/headers/location.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"refresh\", func(t *testing.T) {\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{\"Refresh\": []string{\"999; url=/test/headers/refresh.found\"}}}}\n\t\tnavigationRequests := headerRefreshParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/headers/refresh.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n}\n\nfunc TestBodyParsers(t *testing.T) {\n\tparsed, _ := urlutil.Parse(\"https://security-crawl-maze.app/html/body/xyz/\")\n\n\tt.Run(\"a\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<a href=/test/html/body/a/href.found>\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyATagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/a/href.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(\"<a ping=/test/html/body/a/ping.found>\"))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyATagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/a/ping.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"background\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<body background=\\\"/test/html/body/background.found\\\"></body>\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyBackgroundTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/background.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"blockquote\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<blockquote cite=\"/test/html/body/blockquote/cite.found\"></blockquote>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyBlockquoteCiteTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/blockquote/cite.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"area\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<map name=\"map\">\n\t\t<area ping=\"/test/html/body/map/area/ping.found\" shape=\"rect\" coords=\"0,0,150,150\" href=\"#\">\n\t  </map>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyMapAreaPingTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/map/area/ping.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"audio\", func(t *testing.T) {\n\t\tt.Run(\"src\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<audio src=\\\"/test/html/body/audio/src.found\\\"></audio>\"))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyAudioTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/audio/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t})\n\t\tt.Run(\"source\", func(t *testing.T) {\n\t\t\tt.Run(\"src\", func(t *testing.T) {\n\t\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<audio controls><source src=\\\"/test/html/body/audio/source/src.found\\\" type=\\\"audio/mpeg\\\"></audio>\"))\n\t\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\t\tnavigationRequests := bodyAudioTagParser(resp)\n\t\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/audio/source/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t\t})\n\t\t\tt.Run(\"srcset\", func(t *testing.T) {\n\t\t\t\tvar gotURL []string\n\t\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<audio controls>\n\t\t\t\t<source srcset=\"/test/html/body/audio/source/srcset1x.found 1x,\n\t\t\t\t\t\t\t\t/test/html/body/audio/source/srcset2x.found 2x\">\n\t\t\t</audio>`))\n\t\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\t\tfor _, navigationRequest := range bodyAudioTagParser(resp) {\n\t\t\t\t\tgotURL = append(gotURL, navigationRequest.URL)\n\t\t\t\t}\n\t\t\t\trequire.ElementsMatch(t, []string{\n\t\t\t\t\t\"https://security-crawl-maze.app/test/html/body/audio/source/srcset1x.found\",\n\t\t\t\t\t\"https://security-crawl-maze.app/test/html/body/audio/source/srcset2x.found\",\n\t\t\t\t}, gotURL, \"could not get correct url\")\n\t\t\t})\n\t\t})\n\t})\n\tt.Run(\"img\", func(t *testing.T) {\n\t\tt.Run(\"dynsrc\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<img dynsrc=\"/test/html/body/img/dynsrc.found\">`))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyImgTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/img/dynsrc.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t})\n\t\tt.Run(\"longdesc\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<img alt=\"\" src=\"#\" longdesc=\"/test/html/body/img/longdesc.found\">`))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyImgTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/img/longdesc.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t})\n\t\tt.Run(\"lowsrc\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<img lowsrc=\"/test/html/body/img/lowsrc.found\">`))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyImgTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/img/lowsrc.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t})\n\t\tt.Run(\"src\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<img src=\"/test/html/body/img/src.found\">`))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyImgTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/img/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t})\n\t\tt.Run(\"srcset\", func(t *testing.T) {\n\t\t\tvar gotURL []string\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<img srcset=\"/test/html/body/img/srcset1x.found 1x,\n\t\t\t\t/test/html/body/img/srcset2x.found 2x\">`))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tfor _, navigationResponse := range bodyImgTagParser(resp) {\n\t\t\t\tgotURL = append(gotURL, navigationResponse.URL)\n\t\t\t}\n\t\t\trequire.ElementsMatch(t, []string{\n\t\t\t\t\"https://security-crawl-maze.app/test/html/body/img/srcset1x.found\",\n\t\t\t\t\"https://security-crawl-maze.app/test/html/body/img/srcset2x.found\",\n\t\t\t}, gotURL, \"could not get correct url\")\n\t\t})\n\t})\n\tt.Run(\"html-body\", func(t *testing.T) {\n\t\t// TODO: Fix parsing\n\t\t//\n\t\t// parsed, _ = url.Parse(\"https://security-crawl-maze.app/html/body/frameset/frame/src.html\")\n\t\t// var gotURL []string\n\t\t// resp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Body: []byte(`<p>\n\t\t// \tThe test contains an inline string with known extension - /string-known-extension.pdf\n\t\t// \tThe test contains an inline string - ./test/html/misc/string/dot-slash-prefix.found\n\t\t// \tThe test contains an inline string - ../test/html/misc/string/dot-dot-slash-prefix.found\n\t\t// \tThe test contains an inline string - http://security-crawl-maze.app/test/html/misc/string/url-string.found\n\t\t//   </p>`), Options: &types.CrawlerOptions{Options: &types.Options{ScrapeJSResponses: true}}}\n\t\t// bodyScrapeEndpointsParser(resp, func(resp navigation.Request) {\n\t\t// \tgotURL = append(gotURL, resp.URL)\n\t\t// })\n\t\t// require.ElementsMatch(t, []string{\n\t\t// \t\"https://security-crawl-maze.app/test/string-known-extension.pdf\",\n\t\t// \t\"https://security-crawl-maze.app/test/html/misc/string/dot-slash-prefix.found\",\n\t\t// \t\"https://security-crawl-maze.app/test/html/misc/string/dot-dot-slash-prefix.found\",\n\t\t// \t\"http://security-crawl-maze.app/test/html/misc/string/url-string.found\",\n\t\t// }, gotURL, \"could not get correct url\")\n\t})\n\tt.Run(\"object\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<object data=\"/test/html/body/object/data.found\"></object>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyObjectTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/object/data.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<object codebase=\"/test/html/body/object/codebase.found\"></object>`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyObjectTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/object/codebase.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<object classid=\"clsid:6BF52A52-394A-11d3-B153-00C04F79FAA6\">\n\t\t<param name=\"ref\" value=\"/test/html/body/object/param/value.found\"></param>\n\t  </object>`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyObjectTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/object/param/value.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"svg\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\t\t<image xlink:href=\"/test/html/body/svg/image/xlink.found\"/>\n\t  </svg>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodySvgTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/svg/image/xlink.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n\t\t<script xlink:href=\"/test/html/body/svg/script/xlink.found\"></script>\n\t  </svg>`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodySvgTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/svg/script/xlink.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"table\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<table background=\"/test/html/body/table/background.found\"></table>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyTableTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/table/background.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<table>\n\t\t<tr>\n\t\t\t<td background=\"/test/html/body/table/td/background.found\"></td>\n\t\t</tr>\n\t</table>`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyTableTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/table/td/background.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"video\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<video poster=\"/test/html/body/video/poster.found\"></video>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyVideoTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/video/poster.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<video src=\"/test/html/body/video/src.found\"></video>`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyVideoTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/video/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<video width=\"320\" height=\"240\" controls>\n\t\t<track src=\"/test/html/body/video/track/src.found\" kind=\"subtitles\" srclang=\"en\" label=\"English\">\n\t</video>`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyVideoTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/video/track/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t})\n\tt.Run(\"applet\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<applet archive=\"/test/html/body/applet/archive.found\"></applet>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyAppletTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/applet/archive.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<applet code = \"Test\" codebase=\"/test/html/body/applet/codebase.found\"></applet>`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyAppletTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/applet/codebase.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"link\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<link rel=\\\"stylesheet\\\" href=\\\"/css/font-face.css\\\">\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyLinkHrefTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/css/font-face.css\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<link rel=\"prefetch\" href=\"/test/html/head/link/href.found\" />`))\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests = bodyLinkHrefTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/head/link/href.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"base\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<base href=\"/test/html/head/base/href.found\">`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyBaseHrefTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/head/base/href.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"manifest\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<html xmlns=\"http://www.w3.org/1999/xhtml\" manifest=\"/test/html/manifest.found\">`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyHtmlManifestTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/manifest.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"doctype\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<!DOCTYPE html SYSTEM \"/test/html/doctype.found\">\n<meta charset=\"utf-8\">`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyHtmlDoctypeTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/doctype.found\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t})\n\tt.Run(\"import\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<IMPORT namespace=\"myNS\" implementation=\"/test/html/head/import/implementation.found\" /></IMPORT>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyImportImplementationTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/head/import/implementation.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"embed\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<embed src=\\\"/test/html/body/embed/src.found\\\"></embed>\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyEmbedTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/embed/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"frame\", func(t *testing.T) {\n\t\t//\tvar gotURL string\n\t\t//\t// TODO: Fix test\n\t\t//\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`\n\t\t//\t<!DOCTYPE html>\n\t\t//\t<meta charset=\"utf-8\">\n\t\t//\t<title>CrawlMaze - Testbed for Web Crawlers - frame tag</title>\n\t\t//\t<h1>src attribute</h1>\n\t\t//\t<frameset>\n\t\t//\t  <frame src=\"/test/html/body/frameset/frame/src.found\"></frame>\n\t\t//\t</frameset>`))\n\t\t//\tresp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t//\tbodyFrameTagParser(resp, func(resp navigation.Request) {\n\t\t//\t\tgotURL = resp.URL\n\t\t//\t})\n\t\t//\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/frameset/frame/src.found\", gotURL, \"could not get correct url\")\n\t})\n\tt.Run(\"iframe\", func(t *testing.T) {\n\t\tt.Run(\"src\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<iframe src=\\\"/test/html/body/iframe/src.found\\\"></iframe>\"))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyIframeTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/iframe/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t})\n\t\tt.Run(\"srcdoc\", func(t *testing.T) {\n\t\t\t//var gotURL string\n\t\t\t//documentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<iframe srcdoc=\\\"<img src=/test/html/body/iframe/srcdoc.found>\\\"></iframe>\"))\n\t\t\t//resp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\t//bodyIframeTagParser(resp, func(resp navigation.Request) {\n\t\t\t//\tgotURL = resp.URL\n\t\t\t//})\n\t\t\t//require.Equal(t, \"https://security-crawl-maze.app/test/html/body/iframe/srcdoc.found\", gotURL, \"could not get correct url\")\n\t\t})\n\t})\n\tt.Run(\"input\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<input type=\\\"image\\\" src=\\\"/test/html/body/input/src.found\\\" name=\\\"test\\\" value=\\\"test\\\">\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyInputSrcTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/input/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"isindex\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<isindex action=\\\"/test/html/body/isindex/action.found\\\"></isindex>\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyIsindexActionTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/isindex/action.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"script\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<script src=\\\"/test/html/body/script/src.found\\\"></script>\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyScriptSrcTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/script/src.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"button\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<form id=\\\"test\\\"><button form=\\\"test\\\" formaction=\\\"/test/html/body/form/button/formaction.found\\\" type=\\\"submit\\\">CLICKME</button></form>\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyButtonFormactionTagParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/form/button/formaction.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\tt.Run(\"form\", func(t *testing.T) {\n\t\tt.Run(\"get\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<form action=\\\"/test/html/body/form/action-get.found\\\" method=\\\"GET\\\"><input type=\\\"text\\\" name=\\\"test1\\\" value=\\\"test\\\"><input type=\\\"text\\\" name=\\\"test2\\\" value=\\\"test\\\"></form>\"))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyFormTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/form/action-get.found?test1=test&test2=test\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t})\n\t\tt.Run(\"post\", func(t *testing.T) {\n\t\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<form action=\\\"/test/html/body/form/action-post.found\\\" method=\\\"POST\\\" enctype=\\\"multipart/form-data\\\"><input type=\\\"text\\\" name=\\\"test1\\\" value=\\\"test\\\"><input type=\\\"text\\\" name=\\\"test2\\\" value=\\\"test\\\"></form>\"))\n\t\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t\tnavigationRequests := bodyFormTagParser(resp)\n\t\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/body/form/action-post.found\", navigationRequests[0].URL, \"could not get correct url\")\n\t\t\trequire.Equal(t, \"POST\", navigationRequests[0].Method, \"could not get correct method\")\n\t\t})\n\t})\n\n\tt.Run(\"meta\", func(t *testing.T) {\n\t\t//\tvar gotURL string\n\t\t//\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<meta http-equiv=\\\"refresh\\\" content=\\\"10; url=/test/html/head/meta/content-redirect.found\\\">\"))\n\t\t//\tresp := navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t//\tbodyMetaContentTagParser(resp, func(resp navigation.Request) {\n\t\t//\t\tgotURL = resp.URL\n\t\t//\t})\n\t\t//\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/head/meta/content-redirect.found\", gotURL, \"could not get correct url\")\n\t\t//\n\t\t//\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'self'; report-uri /test/html/head/meta/content-csp.found\">`))\n\t\t//\tresp = navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t//\tbodyMetaContentTagParser(resp, func(resp navigation.Request) {\n\t\t//\t\tgotURL = resp.URL\n\t\t//\t})\n\t\t//\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/head/meta/content-csp.found\", gotURL, \"could not get correct url\")\n\t\t//\n\t\t//\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<meta name=\"msapplication-config\" content=\"/test/html/head/meta/content-pinned-websites.found\">`))\n\t\t//\tresp = navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t//\tbodyMetaContentTagParser(resp, func(resp navigation.Request) {\n\t\t//\t\tgotURL = resp.URL\n\t\t//\t})\n\t\t//\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/head/meta/content-pinned-websites.found\", gotURL, \"could not get correct url\")\n\t\t//\n\t\t//\tdocumentReader, _ = goquery.NewDocumentFromReader(strings.NewReader(`<meta name=\"copyright\" content=\"<img src='/test/html/head/meta/content-reading-view.found'>\">`))\n\t\t//\tresp = navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\t//\tbodyMetaContentTagParser(resp, func(resp navigation.Request) {\n\t\t//\t\tgotURL = resp.URL\n\t\t//\t})\n\t\t//\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/head/meta/content-reading-view.found\", gotURL, \"could not get correct url\")\n\t})\n}\n\nfunc TestScriptParsers(t *testing.T) {\n\tparsed, _ := urlutil.Parse(\"https://security-crawl-maze.app/html/script/xyz/\")\n\n\tt.Run(\"content\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(\"<script>var endpoint='/test/html/script/content.do';</script>\"))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := scriptContentRegexParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/script/content.do\", navigationRequests[0].URL, \"could not get correct url\")\n\t})\n\n\tt.Run(\"js\", func(t *testing.T) {\n\t\tparsed, _ = urlutil.Parse(\"https://security-crawl-maze.app/html/script/xyz/data.js\")\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Body: \"var endpoint='/test/html/script/body.do';\"}\n\t\tnavigationRequests := scriptJSFileRegexParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/script/body.do\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t\tparsed, _ = urlutil.Parse(\"https://security-crawl-maze.app/html/script/xyz/\")\n\t\tresp = &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}, Header: http.Header{\"Content-Type\": []string{\"application/javascript\"}}}, Body: \"var endpoint='/test/html/script/body-content-type.do';\"}\n\t\tnavigationRequests = scriptJSFileRegexParser(resp)\n\t\trequire.Equal(t, \"https://security-crawl-maze.app/test/html/script/body-content-type.do\", navigationRequests[0].URL, \"could not get correct url\")\n\n\t})\n}\n\nfunc TestRegexBodyParsers(t *testing.T) {\n\tparsed, _ := urlutil.Parse(\"https://security-crawl-maze.app/contact\")\n\tt.Run(\"regexbody\", func(t *testing.T) {\n\t\toutput.CustomFieldsMap = make(map[string]output.CustomFieldConfig)\n\t\tresp := &navigation.Response{\n\t\t\tResp:  &http.Response{Request: &http.Request{URL: parsed.URL}},\n\t\t\tDepth: 0,\n\t\t\tBody:  \"some content contact@example.com\",\n\t\t}\n\n\t\t// set required regex\n\t\toutput.CustomFieldsMap[\"email\"] = output.CustomFieldConfig{\n\t\t\tName:         \"email\",\n\t\t\tType:         \"regex\",\n\t\t\tPart:         \"body\",\n\t\t\tCompileRegex: []*regexp.Regexp{regexp.MustCompile(`([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\\.[a-zA-Z0-9_-]+)`)},\n\t\t}\n\n\t\tnavigationRequests := customFieldRegexParser(resp)\n\t\tvar requireFields = map[string][]string{\"email\": {\"contact@example.com\"}}\n\t\trequire.Equal(t, requireFields, navigationRequests[0].CustomFields, \"could not get correct url\")\n\t})\n\tt.Run(\"regexheader\", func(t *testing.T) {\n\t\toutput.CustomFieldsMap = make(map[string]output.CustomFieldConfig)\n\t\tresp := &navigation.Response{\n\t\t\tResp: &http.Response{Request: &http.Request{URL: parsed.URL},\n\t\t\t\tHeader: http.Header{\n\t\t\t\t\t\"server\": []string{\"ECS (dcb/7F84)\"},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\t// set required regex\n\t\toutput.CustomFieldsMap[\"server\"] = output.CustomFieldConfig{\n\t\t\tName:         \"server\",\n\t\t\tType:         \"regex\",\n\t\t\tPart:         \"header\",\n\t\t\tCompileRegex: []*regexp.Regexp{regexp.MustCompile(`server: ECS`)},\n\t\t}\n\n\t\tnavigationRequests := customFieldRegexParser(resp)\n\t\tvar requireFields = map[string][]string{\"server\": {\"server: ECS\"}}\n\t\trequire.Equal(t, requireFields, navigationRequests[0].CustomFields, \"could not get correct url\")\n\t})\n\n\tt.Run(\"regexresponse\", func(t *testing.T) {\n\t\toutput.CustomFieldsMap = make(map[string]output.CustomFieldConfig)\n\t\tresp := &navigation.Response{\n\t\t\tResp: &http.Response{Request: &http.Request{URL: parsed.URL},\n\t\t\t\tHeader: http.Header{\n\t\t\t\t\t\"server\": []string{\"ECS (dcb/7F84)\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tBody: \"some content contact@example.com\",\n\t\t}\n\n\t\t// set required regex\n\t\toutput.CustomFieldsMap[\"server\"] = output.CustomFieldConfig{\n\t\t\tName:         \"server\",\n\t\t\tType:         \"regex\",\n\t\t\tPart:         \"response\",\n\t\t\tCompileRegex: []*regexp.Regexp{regexp.MustCompile(`ECS`)},\n\t\t}\n\t\toutput.CustomFieldsMap[\"email\"] = output.CustomFieldConfig{\n\t\t\tName:         \"email\",\n\t\t\tType:         \"regex\",\n\t\t\tPart:         \"response\",\n\t\t\tCompileRegex: []*regexp.Regexp{regexp.MustCompile(`([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\\.[a-zA-Z0-9_-]+)`)},\n\t\t}\n\n\t\tnavigationRequests := customFieldRegexParser(resp)\n\t\tvar requireFields = map[string][]string{\"server\": {\"ECS\"}, \"email\": {\"contact@example.com\"}}\n\t\trequire.Equal(t, requireFields, navigationRequests[0].CustomFields, \"could not get correct url\")\n\t})\n}\n\nfunc TestHtmxBodyParser(t *testing.T) {\n\tparsed, _ := urlutil.Parse(\"https://htmx.org/examples/\")\n\n\tt.Run(\"hx-get\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<button hx-get=\"/contact/1/edit\" class=\"btn primary\">Click To Edit</button>`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyHtmxAttrParser(resp)\n\t\trequire.Equal(t, \"https://htmx.org/contact/1/edit\", navigationRequests[0].URL, \"could not get correct url\")\n\t\trequire.Equal(t, \"GET\", navigationRequests[0].Method, \"could not get correct method\")\n\t})\n\tt.Run(\"hx-post\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<form id=\"checked-contacts\" hx-post=\"/users\" hx-swap=\"outerHTML settle:3s\" hx-target=\"#toast\">`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyHtmxAttrParser(resp)\n\t\trequire.Equal(t, \"https://htmx.org/users\", navigationRequests[0].URL, \"could not get correct url\")\n\t\trequire.Equal(t, \"POST\", navigationRequests[0].Method, \"could not get correct method\")\n\t})\n\tt.Run(\"hx-put\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<button hx-put=\"/account\" hx-target=\"body\">`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyHtmxAttrParser(resp)\n\t\trequire.Equal(t, \"https://htmx.org/account\", navigationRequests[0].URL, \"could not get correct url\")\n\t\trequire.Equal(t, \"PUT\", navigationRequests[0].Method, \"could not get correct method\")\n\n\t})\n\tt.Run(\"hx-patch\", func(t *testing.T) {\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(`<button hx-patch=\"/account\" hx-target=\"body\">`))\n\t\tresp := &navigation.Response{Resp: &http.Response{Request: &http.Request{URL: parsed.URL}}, Reader: documentReader}\n\t\tnavigationRequests := bodyHtmxAttrParser(resp)\n\t\trequire.Equal(t, \"https://htmx.org/account\", navigationRequests[0].URL, \"could not get correct url\")\n\t\trequire.Equal(t, \"PATCH\", navigationRequests[0].Method, \"could not get correct method\")\n\t})\n}\n\nfunc TestDataURIFiltering(t *testing.T) {\n\tparsed, _ := urlutil.Parse(\"https://example.com/test\")\n\n\tt.Run(\"data-uri-filtering\", func(t *testing.T) {\n\t\t// Test various HTML elements with data URIs to ensure they're filtered out by ParseResponse\n\t\thtmlContent := `\n\t\t\t<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==\">\n\t\t\t<a href=\"data:text/html,<h1>Hello</h1>\">Data Link</a>\n\t\t\t<link href=\"data:text/css,body{color:red}\">\n\t\t\t<script src=\"data:application/javascript,console.log('test')\"></script>\n\t\t\t<iframe src=\"data:text/html,<p>Test</p>\"></iframe>\n\t\t\t<object data=\"data:application/pdf,test\"></object>\n\t\t\t<embed src=\"data:application/x-shockwave-flash,test\">\n\t\t\t<audio src=\"data:audio/mp3,test\"></audio>\n\t\t\t<video src=\"data:video/mp4,test\"></video>\n\t\t\t<a href=\"mailto:test@example.com\">Email Link</a>\n\t\t\t<a href=\"javascript:alert('test')\">JS Link</a>\n\t\t`\n\n\t\tdocumentReader, _ := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))\n\t\tresp := &navigation.Response{\n\t\t\tResp:   &http.Response{Request: &http.Request{URL: parsed.URL}},\n\t\t\tReader: documentReader,\n\t\t}\n\n\t\t// Test the main ParseResponse function to ensure all invalid URIs are filtered out\n\t\tresponseParser := NewResponseParser()\n\t\tnavigationRequests := responseParser.ParseResponse(resp)\n\t\tfor _, req := range navigationRequests {\n\t\t\trequire.False(t, strings.HasPrefix(req.URL, \"data:\"),\n\t\t\t\t\"Found data URI in ParseResponse results: %s\", req.URL)\n\t\t\trequire.False(t, strings.HasPrefix(req.URL, \"mailto:\"),\n\t\t\t\t\"Found mailto URI in ParseResponse results: %s\", req.URL)\n\t\t\trequire.False(t, strings.HasPrefix(req.URL, \"javascript:\"),\n\t\t\t\t\"Found javascript URI in ParseResponse results: %s\", req.URL)\n\t\t}\n\n\t\t// Ensure we have 0 requests since all should be filtered\n\t\trequire.Equal(t, 0, len(navigationRequests), \"Expected all invalid URIs to be filtered out\")\n\t})\n}\n"
  },
  {
    "path": "pkg/engine/standard/crawl.go",
    "content": "package standard\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/common\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/utils\"\n\t\"github.com/projectdiscovery/retryablehttp-go\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n)\n\n// makeRequest makes a request to a URL returning a response interface.\nfunc (c *Crawler) makeRequest(s *common.CrawlSession, request *navigation.Request) (*navigation.Response, error) {\n\tresponse := &navigation.Response{\n\t\tDepth:        request.Depth + 1,\n\t\tRootHostname: s.Hostname,\n\t}\n\tctx := context.WithValue(s.Ctx, navigation.Depth{}, request.Depth)\n\thttpReq, err := http.NewRequestWithContext(ctx, request.Method, request.URL, nil)\n\tif err != nil {\n\t\treturn response, err\n\t}\n\tif request.Body != \"\" && request.Method != \"GET\" {\n\t\thttpReq.Body = io.NopCloser(strings.NewReader(request.Body))\n\t}\n\treq, err := retryablehttp.FromRequest(httpReq)\n\tif err != nil {\n\t\treturn response, err\n\t}\n\treq.Header.Set(\"User-Agent\", utils.WebUserAgent())\n\n\t// Set the headers for the request.\n\tfor k, v := range request.Headers {\n\t\treq.Header.Set(k, v)\n\t\tif k == \"Host\" {\n\t\t\treq.Host = v\n\t\t}\n\t}\n\n\tfor k, v := range c.Headers {\n\t\treq.Header.Set(k, v)\n\t\tif k == \"Host\" {\n\t\t\treq.Host = v\n\t\t}\n\t}\n\n\t// Apply cookies\n\tif c.Jar != nil {\n\t\tcookies := c.Jar.Cookies(req.Request.URL)\n\t\tfor _, cookie := range cookies {\n\t\t\treq.AddCookie(cookie)\n\t\t}\n\t}\n\n\tresp, err := s.HttpClient.Do(req)\n\tif resp != nil {\n\t\tdefer func() {\n\t\t\tif resp.Body != nil && resp.StatusCode != http.StatusSwitchingProtocols {\n\t\t\t\t_, _ = io.Copy(io.Discard, resp.Body)\n\t\t\t}\n\t\t\t_ = resp.Body.Close()\n\t\t}()\n\t}\n\n\t// Collect cookies from the response\n\tif c.Jar != nil && resp != nil {\n\t\tc.Jar.SetCookies(req.Request.URL, resp.Cookies())\n\t}\n\n\trawRequestBytes, _ := req.Dump()\n\trequest.Raw = string(rawRequestBytes)\n\n\tif err != nil {\n\t\treturn response, err\n\t}\n\n\t// If the response is empty, perform a defensive return.\n\tif resp == nil {\n\t\treturn response, errkit.New(\"standard: nil response from http client\")\n\t}\n\n\tif resp.StatusCode == http.StatusSwitchingProtocols {\n\t\treturn response, nil\n\t}\n\n\tlimitReader := io.LimitReader(resp.Body, int64(c.Options.Options.BodyReadSize))\n\tdata, err := io.ReadAll(limitReader)\n\tif err != nil {\n\t\treturn response, err\n\t}\n\t// Skip unique content filtering if disabled\n\tif !c.Options.Options.DisableUniqueFilter {\n\t\tif !c.Options.UniqueFilter.UniqueContent(data) {\n\t\t\treturn &navigation.Response{}, nil\n\t\t}\n\t}\n\n\tif c.Options.Wappalyzer != nil {\n\t\ttechnologies := c.Options.Wappalyzer.Fingerprint(resp.Header, data)\n\t\tresponse.Technologies = mapsutil.GetKeys(technologies)\n\t}\n\n\tresponse.KnowledgeBase = c.Options.ClassifyPage(string(data))\n\n\t// Restore the read data to resp.Body for further use.\n\tresp.Body = io.NopCloser(strings.NewReader(string(data)))\n\n\tresponse.Body = string(data)\n\tresponse.Resp = resp\n\n\t// First, attempt to parse using goquery. If the parsing fails, simply return safely (to avoid accessing response.Reader before checking err).\n\tdoc, err := goquery.NewDocumentFromReader(bytes.NewReader(data))\n\tif err != nil {\n\t\t// Even if the parsing fails, try to attach the original response for debugging purposes.\n\t\trawResponseBytes, _ := httputil.DumpResponse(resp, true)\n\t\tresponse.Raw = string(rawResponseBytes)\n\t\treturn response, errkit.Wrap(err, \"standard: could not make document from reader\")\n\t}\n\t// Only set the `response.Reader` and its URL after the parsing is successful to avoid accessing a nil pointer.\n\tresponse.Reader = doc\n\tresponse.Reader.Url, _ = url.Parse(request.URL)\n\n\tresponse.StatusCode = resp.StatusCode\n\tresponse.Headers = utils.FlattenHeaders(resp.Header)\n\tif c.Options.Options.FormExtraction {\n\t\tresponse.Forms = append(response.Forms, utils.ParseFormFields(response.Reader)...)\n\t}\n\n\t// Use the actual length of the read data as ContentLength\n\tresp.ContentLength = int64(len(data))\n\tresponse.ContentLength = resp.ContentLength\n\n\trawResponseBytes, _ := httputil.DumpResponse(resp, true)\n\tresponse.Raw = string(rawResponseBytes)\n\n\treturn response, nil\n}\n"
  },
  {
    "path": "pkg/engine/standard/doc.go",
    "content": "// Package standard implements the functionality for a non-headless crawler.\n// It uses net/http for making requests and goquery for scraping web page HTML.\npackage standard\n"
  },
  {
    "path": "pkg/engine/standard/standard.go",
    "content": "package standard\n\nimport (\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/common\"\n\t\"github.com/projectdiscovery/katana/pkg/types\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n)\n\n// Crawler is a standard crawler instance\ntype Crawler struct {\n\t*common.Shared\n}\n\n// New returns a new standard crawler instance\nfunc New(options *types.CrawlerOptions) (*Crawler, error) {\n\tshared, err := common.NewShared(options)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"standard\")\n\t}\n\treturn &Crawler{Shared: shared}, nil\n}\n\n// Close closes the crawler process\nfunc (c *Crawler) Close() error {\n\treturn nil\n}\n\n// Crawl crawls a URL with the specified options\nfunc (c *Crawler) Crawl(rootURL string) error {\n\tcrawlSession, err := c.NewCrawlSessionWithURL(rootURL)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"standard\")\n\t}\n\tdefer crawlSession.CancelFunc()\n\tgologger.Info().Msgf(\"Started standard crawling for => %v\", rootURL)\n\tif err := c.Do(crawlSession, c.makeRequest); err != nil {\n\t\treturn errkit.Wrap(err, \"standard\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/navigation/request.go",
    "content": "package navigation\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n)\n\n// Depth is the depth of a navigation\ntype Depth struct{}\n\n// Request is a navigation request for the crawler\ntype Request struct {\n\tMethod         string              `json:\"method,omitempty\"`\n\tURL            string              `json:\"endpoint,omitempty\"`\n\tBody           string              `json:\"body,omitempty\"`\n\tDepth          int                 `json:\"-\"`\n\tSkipValidation bool                `json:\"-\"`\n\tHeaders        map[string]string   `json:\"headers,omitempty\"`\n\tTag            string              `json:\"tag,omitempty\"`\n\tAttribute      string              `json:\"attribute,omitempty\"`\n\tRootHostname   string              `json:\"-\"`\n\tSource         string              `json:\"source,omitempty\"`\n\tCustomFields   map[string][]string `json:\"custom_fields,omitempty\"`\n\tRaw            string              `json:\"raw,omitempty\"`\n}\n\n// RequestURL returns the request URL for the navigation\nfunc (n *Request) RequestURL() string {\n\tswitch n.Method {\n\tcase \"GET\":\n\t\treturn n.URL\n\tcase \"POST\":\n\t\tbuilder := &strings.Builder{}\n\t\tbuilder.WriteString(n.URL)\n\t\tbuilder.WriteString(\":\")\n\t\tbuilder.WriteString(n.Body)\n\t\tbuiltURL := builder.String()\n\t\treturn builtURL\n\t}\n\treturn \"\"\n}\n\n// newNavigationRequestURL generates a navigation request from a relative URL\nfunc NewNavigationRequestURLFromResponse(path, source, tag, attribute string, resp *Response) *Request {\n\trequestURL := resp.AbsoluteURL(path)\n\trequest := &Request{\n\t\tMethod:       http.MethodGet,\n\t\tURL:          requestURL,\n\t\tRootHostname: resp.RootHostname,\n\t\tDepth:        resp.Depth,\n\t\tSource:       source,\n\t\tAttribute:    attribute,\n\t\tTag:          tag,\n\t}\n\treturn request\n}\n"
  },
  {
    "path": "pkg/navigation/response.go",
    "content": "package navigation\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\tjsoniter \"github.com/json-iterator/go\"\n)\n\ntype Headers map[string]string\n\ntype Form struct {\n\tMethod     string   `json:\"method,omitempty\"`\n\tAction     string   `json:\"action,omitempty\"`\n\tEnctype    string   `json:\"enctype,omitempty\"`\n\tParameters []string `json:\"parameters,omitempty\"`\n}\n\nfunc (h *Headers) MarshalJSON() ([]byte, error) {\n\thCopy := make(Headers)\n\tfor k, v := range *h {\n\t\tk := strings.ToLower(k)\n\t\thCopy[k] = v\n\t}\n\treturn jsoniter.Marshal(hCopy)\n}\n\n// Response is a response generated from crawler navigation\ntype Response struct {\n\tResp               *http.Response    `json:\"-\"`\n\tDepth              int               `json:\"-\"`\n\tReader             *goquery.Document `json:\"-\"`\n\tStatusCode         int               `json:\"status_code,omitempty\"`\n\tHeaders            Headers           `json:\"headers,omitempty\"`\n\tBody               string            `json:\"body,omitempty\"`\n\tContentLength      int64             `json:\"content_length,omitempty\"`\n\tRootHostname       string            `json:\"-\"`\n\tTechnologies       []string          `json:\"technologies,omitempty\"`\n\tRaw                string            `json:\"raw,omitempty\"`\n\tForms              []Form            `json:\"forms,omitempty\"`\n\tXhrRequests        []Request         `json:\"xhr_requests,omitempty\"`\n\tStoredResponsePath string            `json:\"stored_response_path,omitempty\"`\n\tKnowledgeBase      map[string]any    `json:\"knowledgebase,omitempty\"`\n}\n\nfunc (n Response) AbsoluteURL(path string) string {\n\tif strings.HasPrefix(path, \"#\") {\n\t\treturn \"\"\n\t}\n\n\tabsURL, err := n.Resp.Request.URL.Parse(path)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tabsURL.Fragment = \"\"\n\tif absURL.Scheme == \"//\" {\n\t\tabsURL.Scheme = n.Resp.Request.URL.Scheme\n\t}\n\tfinal := absURL.String()\n\treturn final\n}\n\nfunc (n Response) IsRedirect() bool {\n\treturn n.StatusCode >= 300 && n.StatusCode <= 399\n}\n"
  },
  {
    "path": "pkg/output/custom_field.go",
    "content": "package output\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tsliceutil \"github.com/projectdiscovery/utils/slice\"\n\t\"gopkg.in/yaml.v2\"\n)\n\n// CustomFieldsMap is the global custom field data instance\n// it is used for parsing the header and body of request\nvar CustomFieldsMap = make(map[string]CustomFieldConfig)\n\ntype Part string\n\nconst (\n\t// RequestPart is the part of request\n\tHeader   Part = \"header\"\n\tBody     Part = \"body\"\n\tResponse Part = \"response\"\n)\n\n// CustomFieldConfig contains suggestions for field filling\ntype CustomFieldConfig struct {\n\tName         string           `yaml:\"name,omitempty\"`\n\tType         string           `yaml:\"type,omitempty\"`\n\tPart         string           `yaml:\"part,omitempty\"`\n\tGroup        int              `yaml:\"group,omitempty\"`\n\tRegex        []string         `yaml:\"regex,omitempty\"`\n\tCompileRegex []*regexp.Regexp `yaml:\"-\"`\n}\n\nvar DefaultFieldConfigData = []CustomFieldConfig{\n\t{\n\t\tName:  \"email\",\n\t\tType:  \"regex\",\n\t\tPart:  Response.ToString(),\n\t\tRegex: []string{`([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\\.[a-zA-Z0-9_-]+)`},\n\t},\n}\n\nfunc (c *CustomFieldConfig) SetCompiledRegexp(r *regexp.Regexp) {\n\tc.CompileRegex = append(c.CompileRegex, r)\n}\n\nfunc (c *CustomFieldConfig) GetName() string {\n\treturn c.Name\n}\n\nfunc parseCustomFieldName(filePath string) error {\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"customfield: could not read field config\")\n\t}\n\tdefer func() {\n\t\tif err := file.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing file: %v\\n\", err)\n\t\t}\n\t}()\n\n\tvar data []CustomFieldConfig\n\tif err := yaml.NewDecoder(file).Decode(&data); err != nil {\n\t\treturn errkit.Wrap(err, \"customfield: could not decode field config\")\n\t}\n\tpassedCustomFieldMap := make(map[string]CustomFieldConfig)\n\tfor _, item := range data {\n\t\tif !regexp.MustCompile(`^[A-Za-z0-9_-]+$`).MatchString(item.Name) {\n\t\t\treturn errkit.Newf(\"customfield: wrong custom field name %s\", item.Name)\n\t\t}\n\t\t// check custom field name is pre-defined or not\n\t\tif sliceutil.Contains(FieldNames, item.Name) {\n\t\t\treturn errkit.Newf(\"customfield: could not register custom field. \\\"%s\\\" already pre-defined field\", item.Name)\n\t\t}\n\t\t// check custom field name should be unique\n\t\tif _, ok := passedCustomFieldMap[item.Name]; ok {\n\t\t\treturn errkit.Newf(\"customfield: could not register custom field. \\\"%s\\\" custom field already exists\", item.Name)\n\t\t}\n\t\tpassedCustomFieldMap[item.Name] = item\n\t}\n\treturn nil\n}\n\nfunc loadCustomFields(filePath string, fields string) error {\n\tvar err error\n\n\tfile, err := os.Open(filePath)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"customfield: could not read field config\")\n\t}\n\tdefer func() {\n\t\tif err := file.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing file: %v\\n\", err)\n\t\t}\n\t}()\n\n\tvar data []CustomFieldConfig\n\t// read the field config file\n\tif err := yaml.NewDecoder(file).Decode(&data); err != nil {\n\t\treturn errkit.Wrap(err, \"customfield: could not decode field config\")\n\t}\n\tfor _, item := range data {\n\t\tfor _, rg := range item.Regex {\n\t\t\tregex, err := regexp.Compile(rg)\n\t\t\tif err != nil {\n\t\t\t\treturn errkit.Wrap(err, \"customfield: could not parse regex in field config\")\n\t\t\t}\n\t\t\titem.SetCompiledRegexp(regex)\n\t\t}\n\t\tif item.Part == \"\" {\n\t\t\titem.Part = Response.ToString()\n\t\t}\n\t\tCustomFieldsMap[item.Name] = item\n\t}\n\treturn nil\n}\n\nfunc initCustomFieldConfigFile() (string, error) {\n\thomedir, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn \"\", errkit.Wrap(err, \"customfield: could not get home directory\")\n\t}\n\tdefaultConfig := filepath.Join(homedir, \".config\", \"katana\", \"field-config.yaml\")\n\n\tif fileutil.FileExists(defaultConfig) {\n\t\treturn defaultConfig, nil\n\t}\n\tif err := os.MkdirAll(filepath.Dir(defaultConfig), 0775); err != nil {\n\t\treturn \"\", err\n\t}\n\tcustomFieldConfig, err := os.Create(defaultConfig)\n\tif err != nil {\n\t\treturn \"\", errkit.Wrap(err, \"customfield: could not get home directory\")\n\t}\n\tdefer func() {\n\t\tif err := customFieldConfig.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing custom field config: %v\\n\", err)\n\t\t}\n\t}()\n\n\terr = yaml.NewEncoder(customFieldConfig).Encode(DefaultFieldConfigData)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn defaultConfig, nil\n}\n\nfunc (p Part) ToString() string {\n\treturn string(p)\n}\n"
  },
  {
    "path": "pkg/output/error.go",
    "content": "package output\n\nimport \"time\"\n\ntype Error struct {\n\tTimestamp time.Time `json:\"timestamp,omitempty\"`\n\tEndpoint  string    `json:\"endpoint,omitempty\"`\n\tSource    string    `json:\"source,omitempty\"`\n\tError     string    `json:\"error,omitempty\"`\n}\n"
  },
  {
    "path": "pkg/output/fields.go",
    "content": "package output\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"golang.org/x/net/publicsuffix\"\n)\n\n// FieldNames is a list of supported field names\nvar FieldNames = []string{\n\t\"url\",\n\t\"path\",\n\t\"fqdn\",\n\t\"rdn\",\n\t\"rurl\",\n\t\"qurl\",\n\t\"qpath\",\n\t\"file\",\n\t\"ufile\",\n\t\"key\",\n\t\"value\",\n\t\"kv\",\n\t\"dir\",\n\t\"udir\",\n}\n\ntype fieldOutput struct {\n\tfield string\n\tvalue string\n}\n\n// validateFieldNames validates provided field names\nfunc validateFieldNames(names string) error {\n\tparts := strings.Split(names, \",\")\n\tif len(parts) == 0 {\n\t\treturn errkit.Newf(\"customfield: no field names provided: %s\", names)\n\t}\n\tuniqueFields := make(map[string]struct{})\n\tfor _, field := range FieldNames {\n\t\tuniqueFields[field] = struct{}{}\n\t}\n\tfor _, field := range CustomFieldsMap {\n\t\tuniqueFields[field.Name] = struct{}{}\n\t}\n\tfor _, part := range parts {\n\t\tif _, ok := uniqueFields[part]; !ok {\n\t\t\treturn errkit.Newf(\"customfield: invalid field %s specified: %s\", part, names)\n\t\t}\n\t}\n\treturn nil\n}\n\n// storeFields stores fields for a result into individual files\n// based on name.\nfunc storeFields(output *Result, storeFields []string) {\n\tparsed, err := urlutil.Parse(output.Request.URL)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"storeFields: failed to parse url %v got %v\", output.Request.URL, err)\n\t\treturn\n\t}\n\n\thostname := parsed.Hostname()\n\tetld, _ := publicsuffix.EffectiveTLDPlusOne(hostname)\n\trootURL := fmt.Sprintf(\"%s://%s\", parsed.Scheme, parsed.Host)\n\tfor _, field := range storeFields {\n\t\tif result := getValueForField(output, parsed.URL, hostname, etld, rootURL, field); result != \"\" {\n\t\t\tappendToFileField(parsed.URL, field, result)\n\t\t}\n\t\tif _, ok := CustomFieldsMap[field]; ok {\n\t\t\tresults := getValueForCustomField(output)\n\t\t\tfor _, result := range results {\n\t\t\t\tappendToFileField(parsed.URL, result.field, result.value)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc appendToFileField(parsed *url.URL, field, data string) {\n\tfile, err := os.OpenFile(path.Join(storeFieldDir, fmt.Sprintf(\"%s_%s_%s.txt\", parsed.Scheme, parsed.Hostname(), field)), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer func() {\n\t\tif err := file.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing file: %v\\n\", err)\n\t\t}\n\t}()\n\n\t_, _ = file.WriteString(data)\n\t_, _ = file.Write([]byte(\"\\n\"))\n}\n\n// formatField formats output results based on fields from fieldNames\nfunc formatField(output *Result, fields string) []fieldOutput {\n\tvar svalue []fieldOutput\n\tparsed, _ := urlutil.Parse(output.Request.URL)\n\tif parsed == nil {\n\t\treturn svalue\n\t}\n\n\tqueryBoth := []string{}\n\tqueryKeys := []string{}\n\tqueryValues := []string{}\n\tparsed.Query().Iterate(func(k string, v []string) bool {\n\t\tfor _, value := range v {\n\t\t\tqueryBoth = append(queryBoth, strings.Join([]string{k, value}, \"=\"))\n\t\t}\n\t\tqueryKeys = append(queryKeys, k)\n\t\tqueryValues = append(queryValues, v...)\n\t\treturn true\n\t})\n\tfor _, f := range stringsutil.SplitAny(fields, \",\") {\n\t\tswitch f {\n\t\tcase \"url\":\n\t\t\tsvalue = append(svalue, fieldOutput{field: \"url\", value: output.Request.URL})\n\t\tcase \"rdn\":\n\t\t\thostname := parsed.Hostname()\n\t\t\tetld, _ := publicsuffix.EffectiveTLDPlusOne(hostname)\n\t\t\tsvalue = append(svalue, fieldOutput{field: \"rdn\", value: etld})\n\t\tcase \"path\":\n\t\t\tif parsed.Path != \"\" {\n\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"path\", value: parsed.Path})\n\t\t\t}\n\t\tcase \"fqdn\":\n\t\t\tsvalue = append(svalue, fieldOutput{field: \"fqdn\", value: parsed.Hostname()})\n\t\tcase \"rurl\":\n\t\t\tsvalue = append(svalue, fieldOutput{field: \"rurl\", value: fmt.Sprintf(\"%s://%s\", parsed.Scheme, parsed.Host)})\n\t\tcase \"qpath\":\n\t\t\tif len(queryKeys) > 0 {\n\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"qpath\", value: fmt.Sprintf(\"%s?%s\", parsed.Path, parsed.Query().Encode())})\n\t\t\t}\n\t\tcase \"qurl\":\n\t\t\tif len(queryKeys) > 0 {\n\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"qurl\", value: output.Request.URL})\n\t\t\t}\n\t\tcase \"key\":\n\t\t\tif len(queryKeys) > 0 || len(queryValues) > 0 || len(queryBoth) > 0 {\n\t\t\t\tfor _, k := range queryKeys {\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"key\", value: k})\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"kv\":\n\t\t\tif len(queryKeys) > 0 || len(queryValues) > 0 || len(queryBoth) > 0 {\n\t\t\t\tfor _, k := range queryBoth {\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"kv\", value: k})\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"value\":\n\t\t\tif len(queryKeys) > 0 || len(queryValues) > 0 || len(queryBoth) > 0 {\n\t\t\t\tfor _, k := range queryValues {\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"value\", value: k})\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"file\":\n\t\t\tif parsed.Path != \"\" && parsed.Path != \"/\" {\n\t\t\t\tbasePath := path.Base(parsed.Path)\n\t\t\t\tif strings.Contains(basePath, \".\") {\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"file\", value: basePath})\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"ufile\":\n\t\t\tif parsed.Path != \"\" && parsed.Path != \"/\" {\n\t\t\t\tbasePath := path.Base(parsed.Path)\n\t\t\t\tif strings.Contains(basePath, \".\") {\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"ufile\", value: parsed.String()})\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"udir\":\n\t\t\tif parsed.Path != \"\" && parsed.Path != \"/\" {\n\t\t\t\tif strings.Contains(parsed.Path[1:], \"/\") {\n\t\t\t\t\tdirectory := parsed.Path[:strings.LastIndex(parsed.Path[1:], \"/\")+2]\n\t\t\t\t\trootURL := fmt.Sprintf(\"%s://%s\", parsed.Scheme, parsed.Host)\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"udir\", value: fmt.Sprintf(\"%s%s\", rootURL, directory)})\n\t\t\t\t}\n\t\t\t}\n\t\tcase \"dir\":\n\t\t\tif parsed.Path != \"\" && parsed.Path != \"/\" {\n\t\t\t\tif strings.Contains(parsed.Path[1:], \"/\") {\n\t\t\t\t\tdirectory := parsed.Path[:strings.LastIndex(parsed.Path[1:], \"/\")+2]\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: \"dir\", value: directory})\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tif v, ok := output.Request.CustomFields[f]; ok {\n\t\t\t\tfor _, r := range v {\n\t\t\t\t\tsvalue = append(svalue, fieldOutput{field: f, value: r})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn svalue\n}\n\n// getValueForField returns value for a field\nfunc getValueForField(output *Result, parsed *url.URL, hostname, rdn, rurl, field string) string {\n\tswitch field {\n\tcase \"url\":\n\t\treturn output.Request.URL\n\tcase \"path\":\n\t\treturn parsed.Path\n\tcase \"fqdn\":\n\t\treturn hostname\n\tcase \"rdn\":\n\t\treturn rdn\n\tcase \"rurl\":\n\t\treturn rurl\n\tcase \"ufile\":\n\t\tbasePath := path.Base(parsed.Path)\n\t\tif parsed.Path != \"\" && parsed.Path != \"/\" && strings.Contains(basePath, \".\") {\n\t\t\treturn parsed.String()\n\t\t}\n\tcase \"file\":\n\t\tbasePath := path.Base(parsed.Path)\n\t\tif parsed.Path != \"\" && parsed.Path != \"/\" && strings.Contains(basePath, \".\") {\n\t\t\treturn basePath\n\t\t}\n\tcase \"dir\":\n\t\tif parsed.Path != \"\" && parsed.Path != \"/\" && strings.Contains(parsed.Path[1:], \"/\") {\n\t\t\treturn parsed.Path[:strings.LastIndex(parsed.Path[1:], \"/\")+2]\n\t\t}\n\tcase \"udir\":\n\t\tif parsed.Path != \"\" && parsed.Path != \"/\" && strings.Contains(parsed.Path[1:], \"/\") {\n\t\t\treturn fmt.Sprintf(\"%s%s\", rurl, parsed.Path[:strings.LastIndex(parsed.Path[1:], \"/\")+2])\n\t\t}\n\tcase \"qpath\":\n\t\tif len(parsed.Query()) > 0 {\n\t\t\treturn fmt.Sprintf(\"%s?%s\", parsed.Path, parsed.Query().Encode())\n\t\t}\n\tcase \"qurl\":\n\t\tif len(parsed.Query()) > 0 {\n\t\t\treturn parsed.String()\n\t\t}\n\tcase \"key\":\n\t\tvalues := make([]string, 0, len(parsed.Query()))\n\t\tfor k := range parsed.Query() {\n\t\t\tvalues = append(values, k)\n\t\t}\n\t\treturn strings.Join(values, \"\\n\")\n\tcase \"value\":\n\t\tvalues := make([]string, 0, len(parsed.Query()))\n\t\tfor _, v := range parsed.Query() {\n\t\t\tvalues = append(values, v...)\n\t\t}\n\t\treturn strings.Join(values, \"\\n\")\n\tcase \"kv\":\n\t\tvalues := make([]string, 0, len(parsed.Query()))\n\t\tfor k, v := range parsed.Query() {\n\t\t\tfor _, value := range v {\n\t\t\t\tvalues = append(values, strings.Join([]string{k, value}, \"=\"))\n\t\t\t}\n\t\t}\n\t\treturn strings.Join(values, \"\\n\")\n\t}\n\treturn \"\"\n}\n\nfunc getValueForCustomField(output *Result) []fieldOutput {\n\tvar svalue []fieldOutput\n\tfor k, v := range output.Request.CustomFields {\n\t\tfor _, r := range v {\n\t\t\tsvalue = append(svalue, fieldOutput{field: k, value: r})\n\t\t}\n\t}\n\treturn svalue\n}\n"
  },
  {
    "path": "pkg/output/fields_test.go",
    "content": "package output\n\nimport (\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidateFieldNames(t *testing.T) {\n\terr := validateFieldNames(\"fqdn\")\n\trequire.Nil(t, err, \"got error with valid field\")\n\n\terr = validateFieldNames(\"\")\n\trequire.Error(t, err, \"got no error with blank field\")\n\n\terr = validateFieldNames(\"invalid\")\n\trequire.Error(t, err, \"got no error with invalid field\")\n}\n\nfunc TestFormatField(t *testing.T) {\n\turl := \"https://policies.google.com/terms/file.php?hl=en-IN&fg=1\"\n\ttests := []struct {\n\t\turl    string\n\t\tfields string\n\t\tresult []fieldOutput\n\t}{\n\t\t{url, \"url\", []fieldOutput{{\"url\", url}}},\n\t\t{url, \"path\", []fieldOutput{{\"path\", \"/terms/file.php\"}}},\n\t\t{url, \"fqdn\", []fieldOutput{{\"fqdn\", \"policies.google.com\"}}},\n\t\t{url, \"rdn\", []fieldOutput{{\"rdn\", \"google.com\"}}},\n\t\t{url, \"rurl\", []fieldOutput{{\"rurl\", \"https://policies.google.com\"}}},\n\t\t{url, \"file\", []fieldOutput{{\"file\", \"file.php\"}}},\n\t\t{url, \"key\", []fieldOutput{{\"key\", \"hl\"}, {\"key\", \"fg\"}}},\n\t\t{url, \"kv\", []fieldOutput{{\"kv\", \"hl=en-IN\"}, {\"kv\", \"fg=1\"}}},\n\t\t{url, \"value\", []fieldOutput{{\"value\", \"en-IN\"}, {\"value\", \"1\"}}},\n\t\t{url, \"dir\", []fieldOutput{{\"dir\", \"/terms/\"}}},\n\t\t{url, \"udir\", []fieldOutput{{\"udir\", \"https://policies.google.com/terms/\"}}},\n\t}\n\n\tfor _, test := range tests {\n\t\tresult := formatField(&Result{Request: &navigation.Request{URL: test.url}}, test.fields)\n\t\trequire.ElementsMatch(t, test.result, result, \"could not equal value\")\n\t}\n}\n"
  },
  {
    "path": "pkg/output/file_writer.go",
    "content": "package output\n\nimport (\n\t\"bufio\"\n\t\"os\"\n)\n\n// fileWriter is a concurrent file based output writer.\ntype fileWriter struct {\n\tfile   *os.File\n\twriter *bufio.Writer\n}\n\n// NewFileOutputWriter creates a new buffered writer for a file\nfunc newFileOutputWriter(file string) (*fileWriter, error) {\n\toutput, err := os.Create(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &fileWriter{file: output, writer: bufio.NewWriter(output)}, nil\n}\n\n// WriteString writes an output to the underlying file\nfunc (w *fileWriter) Write(data []byte) error {\n\t_, err := w.writer.Write(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = w.writer.WriteRune('\\n')\n\treturn err\n}\n\n// Close closes the underlying writer flushing everything to disk\nfunc (w *fileWriter) Close() error {\n\t_ = w.writer.Flush()\n\t//nolint:errcheck // we don't care whether sync failed or succeeded.\n\tw.file.Sync()\n\treturn w.file.Close()\n}\n"
  },
  {
    "path": "pkg/output/format_json.go",
    "content": "package output\n\nimport (\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/projectdiscovery/utils/structs\"\n)\n\n// formatJSON formats the output for json based formatting\nfunc (w *StandardWriter) formatJSON(output *Result) ([]byte, error) {\n\tfinalOrdMap, err := structs.FilterStructToMap(*output, nil, w.excludeOutputFields)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, ok := finalOrdMap.Get(\"request\"); ok && output.Request != nil {\n\t\treqOrdMap, err := structs.FilterStructToMap(*output.Request, nil, w.excludeOutputFields)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif reqOrdMap.Len() > 0 {\n\t\t\tfinalOrdMap.Set(\"request\", reqOrdMap)\n\t\t} else {\n\t\t\tfinalOrdMap.Delete(\"request\")\n\t\t}\n\t}\n\n\tif _, ok := finalOrdMap.Get(\"response\"); ok && output.Response != nil {\n\t\trespOrdMap, err := structs.FilterStructToMap(*output.Response, nil, w.excludeOutputFields)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif respOrdMap.Len() > 0 {\n\t\t\tfinalOrdMap.Set(\"response\", respOrdMap)\n\t\t} else {\n\t\t\tfinalOrdMap.Delete(\"response\")\n\t\t}\n\t}\n\n\treturn jsoniter.Marshal(finalOrdMap)\n}\n"
  },
  {
    "path": "pkg/output/format_screen.go",
    "content": "package output\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"strconv\"\n)\n\n// formatScreen formats the output for showing on screen.\nfunc (w *StandardWriter) formatScreen(output *Result) ([]byte, error) {\n\tbuilder := &bytes.Buffer{}\n\tif w.fields != \"\" {\n\t\tresult := formatField(output, w.fields)\n\t\tfor _, fop := range result {\n\t\t\tif w.verbose {\n\t\t\t\tbuilder.WriteRune('[')\n\t\t\t\tbuilder.WriteString(w.aurora.Blue(fop.field).String())\n\t\t\t\tbuilder.WriteRune(']')\n\t\t\t\tbuilder.WriteRune(' ')\n\t\t\t}\n\t\t\tfmt.Fprintf(builder, \"%s\\n\", fop.value)\n\t\t}\n\t\treturn builder.Bytes(), nil\n\t}\n\n\tif w.verbose && output.Request.Tag != \"\" {\n\t\tbuilder.WriteRune('[')\n\t\tbuilder.WriteString(w.aurora.Blue(output.Request.Tag).String())\n\t\tbuilder.WriteRune(']')\n\t\tbuilder.WriteRune(' ')\n\t}\n\n\tif output.Request.Method != \"\" && w.verbose {\n\t\tbuilder.WriteRune('[')\n\t\tbuilder.WriteString(w.aurora.Green(output.Request.Method).String())\n\t\tbuilder.WriteRune(']')\n\t\tbuilder.WriteRune(' ')\n\t}\n\n\tbuilder.WriteString(output.Request.URL)\n\n\tif output.Request.Body != \"\" && w.verbose {\n\t\tbuilder.WriteRune(' ')\n\t\tbuilder.WriteRune('[')\n\t\tbuilder.WriteString(output.Request.Body)\n\t\tbuilder.WriteRune(']')\n\t}\n\n\tif w.verbose {\n\t\tbuilder.WriteRune(' ')\n\t\tbuilder.WriteRune('[')\n\t\tbuilder.WriteString(w.aurora.Yellow(\"depth:\" + strconv.Itoa(output.Request.Depth)).String())\n\t\tbuilder.WriteRune(']')\n\t}\n\n\treturn builder.Bytes(), nil\n}\n"
  },
  {
    "path": "pkg/output/format_template.go",
    "content": "package output\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/valyala/fasttemplate\"\n)\n\nfunc (w *StandardWriter) formatTemplate(output *Result) ([]byte, error) {\n\tvar fieldOutputs []fieldOutput\n\tfieldNames := strings.Join(FieldNames, \",\")\n\tfieldOutputs = formatField(output, fieldNames)\n\tfieldOutputs = append(fieldOutputs, getValueForCustomField(output)...)\n\n\tfieldsMap := make(map[string]string)\n\tfor _, fo := range fieldOutputs {\n\t\tfieldsMap[fo.field] = fo.value\n\t}\n\n\terrUnknownTag := errors.New(\"unknown tag\")\n\n\ttagFn := fasttemplate.TagFunc(func(w io.Writer, tag string) (int, error) {\n\t\tvalue, ok := fieldsMap[tag]\n\t\tif !ok {\n\t\t\treturn 0, fmt.Errorf(\"%w %q\", errUnknownTag, tag)\n\t\t}\n\t\treturn w.Write([]byte(value))\n\t})\n\n\tout, err := w.outputTemplate.ExecuteFuncStringWithErr(tagFn)\n\tif err != nil {\n\t\tif errors.Is(err, errUnknownTag) {\n\t\t\t// If there is an unknown tag, we just ignore it.\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\n\treturn []byte(out), nil\n}\n"
  },
  {
    "path": "pkg/output/options.go",
    "content": "package output\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/projectdiscovery/katana/pkg/utils/extensions\"\n)\n\n// Options contains the configuration options for output writer\ntype Options struct {\n\tColors                bool\n\tJSON                  bool\n\tVerbose               bool\n\tStoreResponse         bool\n\tNoClobber             bool\n\tOmitRaw               bool\n\tOmitBody              bool\n\tOutputFile            string\n\tFields                string\n\tStoreFields           string\n\tStoreResponseDir      string\n\tStoreFieldDir         string\n\tFieldConfig           string\n\tErrorLogFile          string\n\tMatchRegex            []*regexp.Regexp\n\tFilterRegex           []*regexp.Regexp\n\tExtensionValidator    *extensions.Validator\n\tOutputTemplate        string\n\tOutputMatchCondition  string\n\tOutputFilterCondition string\n\tExcludeOutputFields   []string\n\tFilterPageType        []string\n}\n"
  },
  {
    "path": "pkg/output/output.go",
    "content": "package output\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/logrusorgru/aurora\"\n\t\"github.com/mitchellh/mapstructure\"\n\t\"github.com/projectdiscovery/dsl\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n\t\"github.com/projectdiscovery/katana/pkg/utils/extensions\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\t\"github.com/stoewer/go-strcase\"\n\t\"github.com/valyala/fasttemplate\"\n)\n\nconst (\n\tindexFile          = \"index.txt\"\n\tDefaultResponseDir = \"katana_response\"\n)\n\nvar (\n\tstoreFieldDir    = \"katana_field\"\n\tdecolorizerRegex = regexp.MustCompile(`\\x1B\\[[0-9;]*[a-zA-Z]`)\n)\n\n// Writer is an interface which writes output to somewhere for katana events.\ntype Writer interface {\n\t// Close closes the output writer interface\n\tClose() error\n\t// Write writes the event to file and/or screen.\n\tWrite(*Result) error\n\tWriteErr(*Error) error\n}\n\n// StandardWriter is an standard output writer structure\ntype StandardWriter struct {\n\tstoreFields           []string\n\tfields                string\n\tjson                  bool\n\tverbose               bool\n\taurora                aurora.Aurora\n\toutputFile            *fileWriter\n\toutputMutex           *sync.Mutex\n\tstoreResponse         bool\n\tstoreResponseDir      string\n\tnoClobber             bool\n\tomitRaw               bool\n\tomitBody              bool\n\terrorFile             *fileWriter\n\tmatchRegex            []*regexp.Regexp\n\tfilterRegex           []*regexp.Regexp\n\textensionValidator    *extensions.Validator\n\toutputTemplate        *fasttemplate.Template\n\toutputMatchCondition  string\n\toutputFilterCondition string\n\texcludeOutputFields   []string\n\tfilterPageType        []string\n}\n\n// New returns a new output writer instance\nfunc New(options Options) (Writer, error) {\n\twriter := &StandardWriter{\n\t\tfields:                options.Fields,\n\t\tjson:                  options.JSON,\n\t\tverbose:               options.Verbose,\n\t\taurora:                aurora.NewAurora(options.Colors),\n\t\toutputMutex:           &sync.Mutex{},\n\t\tstoreResponse:         options.StoreResponse,\n\t\tstoreResponseDir:      options.StoreResponseDir,\n\t\tnoClobber:             options.NoClobber,\n\t\tomitRaw:               options.OmitRaw,\n\t\tomitBody:              options.OmitBody,\n\t\tmatchRegex:            options.MatchRegex,\n\t\tfilterRegex:           options.FilterRegex,\n\t\textensionValidator:    options.ExtensionValidator,\n\t\toutputMatchCondition:  options.OutputMatchCondition,\n\t\toutputFilterCondition: options.OutputFilterCondition,\n\t\texcludeOutputFields:   options.ExcludeOutputFields,\n\t\tfilterPageType:        options.FilterPageType,\n\t}\n\n\tif options.StoreFieldDir != \"\" {\n\t\tstoreFieldDir = options.StoreFieldDir\n\t}\n\t// if fieldConfig empty get the default file\n\tif options.FieldConfig == \"\" {\n\t\tvar err error\n\t\toptions.FieldConfig, err = initCustomFieldConfigFile()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\terr := parseCustomFieldName(options.FieldConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = loadCustomFields(options.FieldConfig, fmt.Sprintf(\"%s,%s\", options.Fields, options.StoreFields))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Perform validations for fields and store-fields\n\tif options.Fields != \"\" {\n\t\tif err := validateFieldNames(options.Fields); err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"output: could not validate fields\")\n\t\t}\n\t}\n\tif options.StoreFields != \"\" {\n\t\t_ = os.MkdirAll(storeFieldDir, os.ModePerm)\n\t\tif err := validateFieldNames(options.StoreFields); err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"output: could not validate store fields\")\n\t\t}\n\t\twriter.storeFields = append(writer.storeFields, strings.Split(options.StoreFields, \",\")...)\n\t}\n\tif options.OutputFile != \"\" {\n\t\toutput, err := newFileOutputWriter(options.OutputFile)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"output: could not create output file\")\n\t\t}\n\t\twriter.outputFile = output\n\t}\n\tif options.StoreResponse {\n\t\twriter.storeResponseDir = DefaultResponseDir\n\t\tif options.StoreResponseDir != DefaultResponseDir && options.StoreResponseDir != \"\" {\n\t\t\twriter.storeResponseDir = options.StoreResponseDir\n\t\t}\n\t\tif options.NoClobber {\n\t\t\twriter.storeResponseDir = createDirNameNoClobber(writer.storeResponseDir)\n\t\t\t_ = os.MkdirAll(writer.storeResponseDir, os.ModePerm)\n\t\t} else {\n\t\t\tremoveDirsWithSuffix(writer.storeResponseDir)\n\t\t\t_ = os.MkdirAll(writer.storeResponseDir, os.ModePerm)\n\t\t}\n\t\t// todo: the index file seems never used?\n\t\t_, err := newFileOutputWriter(filepath.Join(writer.storeResponseDir, indexFile))\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"output: could not create index file\")\n\t\t}\n\t}\n\tif options.ErrorLogFile != \"\" {\n\t\terrorFile, err := newFileOutputWriter(options.ErrorLogFile)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"output: could not create error file\")\n\t\t}\n\n\t\twriter.errorFile = errorFile\n\t}\n\tif options.OutputTemplate != \"\" {\n\t\twriter.outputTemplate, err = fasttemplate.NewTemplate(options.OutputTemplate, \"{{\", \"}}\")\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"output: could not create output format template\")\n\t\t}\n\t}\n\treturn writer, nil\n}\n\n// Write writes the result to file and/or screen.\nfunc (w *StandardWriter) Write(result *Result) error {\n\tif result == nil {\n\t\treturn errors.New(\"result is nil\")\n\t}\n\n\tif len(w.storeFields) > 0 {\n\t\tstoreFields(result, w.storeFields)\n\t}\n\n\tif !w.extensionValidator.ValidatePath(result.Request.URL) {\n\t\treturn errors.New(\"result does not match extension filter\")\n\t}\n\n\tif !w.matchOutput(result) {\n\t\treturn errors.New(\"result does not match output\")\n\t}\n\tif w.filterOutput(result) {\n\t\treturn errors.New(\"result is filtered out\")\n\t}\n\tif len(w.filterPageType) > 0 && result.Response != nil && result.Response.KnowledgeBase != nil {\n\t\tif pageType, ok := result.Response.KnowledgeBase[\"PageType\"].(string); ok {\n\t\t\tfor _, ft := range w.filterPageType {\n\t\t\t\tif strings.EqualFold(pageType, ft) {\n\t\t\t\t\treturn errors.New(\"result filtered by page type\")\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tvar data []byte\n\tvar err error\n\n\tif w.storeResponse && result.HasResponse() {\n\t\tif fileName, fileWriter, err := getResponseFile(w.storeResponseDir, result.Response.Resp.Request.URL.String()); err == nil {\n\t\t\tif absPath, err := filepath.Abs(fileName); err == nil {\n\t\t\t\tfileName = absPath\n\t\t\t}\n\t\t\tresult.Response.StoredResponsePath = fileName\n\t\t\tdata, err := w.formatResult(result)\n\t\t\tif err != nil {\n\t\t\t\treturn errkit.Wrap(err, \"output: could not store response\")\n\t\t\t}\n\t\t\tif err := updateIndex(w.storeResponseDir, result); err != nil {\n\t\t\t\treturn errkit.Wrap(err, \"output: could not store response\")\n\t\t\t}\n\t\t\tif err := fileWriter.Write(data); err != nil {\n\t\t\t\treturn errkit.Wrap(err, \"output: could not store response\")\n\t\t\t}\n\t\t\t_ = fileWriter.Close()\n\t\t}\n\t}\n\n\tif w.omitRaw {\n\t\tresult.Request.Raw = \"\"\n\t\tif result.Response != nil {\n\t\t\tresult.Response.Raw = \"\"\n\t\t}\n\t}\n\tif w.omitBody && result.HasResponse() {\n\t\tresult.Response.Body = \"\"\n\t}\n\n\tvar outputKind string\n\n\tswitch {\n\tcase w.outputTemplate != nil:\n\t\toutputKind = \"template\"\n\t\tdata, err = w.formatTemplate(result)\n\tcase w.json:\n\t\toutputKind = \"JSON\"\n\t\tdata, err = w.formatJSON(result)\n\tdefault:\n\t\toutputKind = \"screen\"\n\t\tdata, err = w.formatScreen(result)\n\t}\n\n\tif err != nil {\n\t\treturn errkit.Wrap(err, fmt.Sprintf(\"output: could not format %s output\", outputKind))\n\t}\n\n\tif len(data) == 0 {\n\t\treturn errors.New(\"result is empty\")\n\t}\n\tw.outputMutex.Lock()\n\tdefer w.outputMutex.Unlock()\n\n\tgologger.Silent().Msgf(\"%s\", string(data))\n\tif w.outputFile != nil {\n\t\tif !w.json {\n\t\t\tdata = decolorizerRegex.ReplaceAll(data, []byte(\"\"))\n\t\t}\n\t\tif err := w.outputFile.Write(data); err != nil {\n\t\t\treturn errkit.Wrap(err, \"output: could not write to output\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (w *StandardWriter) WriteErr(errMessage *Error) error {\n\tdata, err := jsoniter.Marshal(errMessage)\n\tif err != nil {\n\t\treturn errkit.Wrap(err, \"output: marshal\")\n\t}\n\tif len(data) == 0 {\n\t\treturn nil\n\t}\n\tw.outputMutex.Lock()\n\tdefer w.outputMutex.Unlock()\n\n\tif w.errorFile != nil {\n\t\tif err := w.errorFile.Write(data); err != nil {\n\t\t\treturn errkit.Wrap(err, \"output: write to error file\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// Close closes the output writer\nfunc (w *StandardWriter) Close() error {\n\tif w.outputFile != nil {\n\t\terr := w.outputFile.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif w.errorFile != nil {\n\t\terr := w.errorFile.Close()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc createDirNameNoClobber(dir string) string {\n\tif !fileutil.FolderExists(dir) {\n\t\treturn dir\n\t}\n\n\tparentDir, dirName := filepath.Dir(dir), filepath.Base(dir)\n\tentries, err := os.ReadDir(parentDir)\n\tif err != nil {\n\t\treturn dirName\n\t}\n\n\thighestNum := 0\n\tregex := regexp.MustCompile(fmt.Sprintf(\"^%s(\\\\d+)$\", regexp.QuoteMeta(dirName)))\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\tname := entry.Name()\n\t\t\tmatches := regex.FindStringSubmatch(name)\n\t\t\tif matches != nil {\n\t\t\t\tif num, err := strconv.Atoi(matches[1]); err == nil && num > highestNum {\n\t\t\t\t\thighestNum = num\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tnewDirName := fmt.Sprintf(\"%s%d\", dirName, highestNum+1)\n\tnewFullPath := filepath.Join(parentDir, newDirName)\n\treturn newFullPath\n}\n\nfunc removeDirsWithSuffix(dir string) {\n\tparentDir, dirName := filepath.Dir(dir), filepath.Base(dir)\n\tentries, _ := os.ReadDir(parentDir)\n\n\tpattern := fmt.Sprintf(\"^%s(\\\\d*)$\", regexp.QuoteMeta(dirName))\n\tregex := regexp.MustCompile(pattern)\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() {\n\t\t\tname := entry.Name()\n\t\t\tif regex.MatchString(name) {\n\t\t\t\tfullPath := filepath.Join(parentDir, name)\n\t\t\t\t_ = os.RemoveAll(fullPath)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// matchOutput checks if the event matches the output regex\nfunc (w *StandardWriter) matchOutput(event *Result) bool {\n\tif w.matchRegex == nil && w.outputMatchCondition == \"\" {\n\t\treturn true\n\t}\n\n\tfor _, regex := range w.matchRegex {\n\t\tif regex.MatchString(event.Request.URL) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif w.outputMatchCondition != \"\" {\n\t\treturn evalDslExpr(event, w.outputMatchCondition)\n\t}\n\n\treturn false\n}\n\n// filterOutput returns true if the event should be filtered out\nfunc (w *StandardWriter) filterOutput(event *Result) bool {\n\tif w.filterRegex == nil && w.outputFilterCondition == \"\" {\n\t\treturn false\n\t}\n\n\tfor _, regex := range w.filterRegex {\n\t\tif regex.MatchString(event.Request.URL) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\tif w.outputFilterCondition != \"\" {\n\t\treturn evalDslExpr(event, w.outputFilterCondition)\n\t}\n\n\treturn false\n}\n\nfunc evalDslExpr(result *Result, dslExpr string) bool {\n\tresultMap, err := resultToMap(*result)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"Could not map result: %s\\n\", err)\n\t\treturn false\n\t}\n\n\tres, err := dsl.EvalExpr(dslExpr, resultMap)\n\tif err != nil && !ignoreErr(err) {\n\t\tgologger.Error().Msgf(\"Could not evaluate DSL expression: %s\\n\", err)\n\t\treturn false\n\t}\n\treturn res == true\n}\n\nfunc resultToMap(result Result) (map[string]interface{}, error) {\n\tresultMap := make(map[string]any)\n\tconfig := &mapstructure.DecoderConfig{\n\t\tTagName: \"json\",\n\t\tResult:  &resultMap,\n\t}\n\tdecoder, err := mapstructure.NewDecoder(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating decoder: %v\", err)\n\t}\n\terr = decoder.Decode(result)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error decoding: %v\", err)\n\t}\n\n\trequestMap := make(map[string]any)\n\tif err := mapstructure.Decode(result.Request, &requestMap); err == nil {\n\t\tfor k, v := range requestMap {\n\t\t\tresultMap[strcase.SnakeCase(k)] = v\n\t\t}\n\t}\n\n\tresponseMap := make(map[string]any)\n\tif err := mapstructure.Decode(result.Response, &responseMap); err == nil {\n\t\tfor k, v := range responseMap {\n\t\t\tif strings.ToLower(k) == \"headers\" {\n\t\t\t\tif headers, ok := v.(navigation.Headers); ok {\n\t\t\t\t\tfor hk, hv := range headers {\n\t\t\t\t\t\tresultMap[strcase.SnakeCase(hk)] = hv\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tresultMap[strcase.SnakeCase(k)] = v\n\t\t\t}\n\t\t}\n\t}\n\n\treturn flatten(resultMap), nil\n}\n\n// mapsutil.Flatten w/o separator\nfunc flatten(m map[string]any) map[string]any {\n\to := make(map[string]any)\n\tfor k, v := range m {\n\t\tswitch child := v.(type) {\n\t\tcase map[string]any:\n\t\t\tnm := flatten(child)\n\t\t\tfor nk, nv := range nm {\n\t\t\t\to[strcase.SnakeCase(nk)] = nv\n\t\t\t}\n\t\tdefault:\n\t\t\to[strcase.SnakeCase(k)] = v\n\t\t}\n\t}\n\treturn o\n}\n\nvar (\n\t// showDSLErr controls whether to show hidden DSL errors or not\n\tshowDSLErr = strings.EqualFold(os.Getenv(\"SHOW_DSL_ERRORS\"), \"true\")\n)\n\n// ignoreErr checks if the error is to be ignored or not\n// Reference: https://github.com/projectdiscovery/katana/pull/537\nfunc ignoreErr(err error) bool {\n\tif showDSLErr {\n\t\treturn false\n\t}\n\tif errors.Is(err, dsl.ErrParsingArg) || strings.Contains(err.Error(), \"No parameter\") {\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/output/responses.go",
    "content": "package output\n\nimport (\n\t\"bytes\"\n\t\"crypto/sha1\"\n\t\"encoding/hex\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\nfunc getResponseHash(URL string) string {\n\thash := sha1.Sum([]byte(URL))\n\treturn hex.EncodeToString(hash[:])\n}\n\nfunc (w *StandardWriter) formatResult(result *Result) ([]byte, error) {\n\tbuilder := &bytes.Buffer{}\n\n\tbuilder.WriteString(result.Request.URL)\n\tbuilder.WriteString(\"\\n\\n\\n\")\n\n\tbuilder.WriteString(result.Request.Raw)\n\n\tbuilder.WriteString(\"\\n\\n\")\n\n\tbuilder.WriteString(result.Response.Raw)\n\n\treturn builder.Bytes(), nil\n}\n\nfunc getResponseHost(URL string) (string, error) {\n\tu, err := urlutil.Parse(URL)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filepath.Clean(strings.ReplaceAll(u.Host, \":\", \"_\")), nil\n}\n\nfunc createHostDir(storeResponseFolder, domain string) string {\n\t_ = os.MkdirAll(filepath.Join(storeResponseFolder, domain), os.ModePerm)\n\treturn filepath.Join(storeResponseFolder, domain)\n}\n\nfunc getResponseFile(storeResponseFolder, URL string) (string, *fileWriter, error) {\n\tdomain, err := getResponseHost(URL)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tfileName := getResponseFileName(storeResponseFolder, domain, URL)\n\toutput, err := newFileOutputWriter(fileName)\n\tif err != nil {\n\t\treturn \"\", nil, errkit.Wrap(err, \"output: could not create output file\")\n\t}\n\n\treturn fileName, output, nil\n}\n\nfunc getResponseFileName(storeResponseFolder, domain, URL string) string {\n\tfolder := createHostDir(storeResponseFolder, domain)\n\tfile := getResponseHash(URL) + \".txt\"\n\treturn filepath.Join(folder, file)\n}\n\nfunc updateIndex(storeResponseFolder string, result *Result) error {\n\tindex, err := os.OpenFile(filepath.Join(storeResponseFolder, indexFile), os.O_APPEND|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\tif err := index.Close(); err != nil {\n\t\t\tgologger.Error().Msgf(\"Error closing index: %v\\n\", err)\n\t\t}\n\t}()\n\n\tbuilder := &bytes.Buffer{}\n\n\tdomain, err := getResponseHost(result.Request.URL)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbuilder.WriteString(getResponseFileName(storeResponseFolder, domain, result.Request.URL))\n\tbuilder.WriteRune(' ')\n\tbuilder.WriteString(result.Request.URL)\n\tbuilder.WriteRune(' ')\n\tbuilder.WriteString(\"(\" + result.Response.Resp.Status + \")\")\n\tbuilder.WriteRune('\\n')\n\n\tif _, writeErr := index.Write(builder.Bytes()); writeErr != nil {\n\t\treturn errkit.Wrap(err, \"output: could not update index\")\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/output/result.go",
    "content": "package output\n\nimport (\n\t\"time\"\n\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\n)\n\n// Result of the crawling\ntype Result struct {\n\tTimestamp time.Time            `json:\"timestamp,omitempty\"`\n\tRequest   *navigation.Request  `json:\"request,omitempty\"`\n\tResponse  *navigation.Response `json:\"response,omitempty\"`\n\tError     string               `json:\"error,omitempty\"`\n}\n\n// HasResponse checks if the result has a valid response\nfunc (r *Result) HasResponse() bool {\n\treturn r.Response != nil && r.Response.Resp != nil\n}\n"
  },
  {
    "path": "pkg/types/crawler_options.go",
    "content": "package types\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"os/user\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/fastdialer/fastdialer\"\n\t\"github.com/projectdiscovery/katana/pkg/engine/parser\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\t\"github.com/projectdiscovery/katana/pkg/utils/extensions\"\n\t\"github.com/projectdiscovery/katana/pkg/utils/filters\"\n\t\"github.com/projectdiscovery/katana/pkg/utils/scope\"\n\t\"github.com/projectdiscovery/ratelimit\"\n\t\"github.com/happyhackingspace/dit\"\n\t\"github.com/projectdiscovery/utils/errkit\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\twappalyzer \"github.com/projectdiscovery/wappalyzergo\"\n)\n\n// CrawlerOptions contains helper utilities for the crawler\ntype CrawlerOptions struct {\n\t// OutputWriter is the interface for writing output\n\tOutputWriter output.Writer\n\t// RateLimit is a mechanism for controlling request rate limit\n\tRateLimit *ratelimit.Limiter\n\t// Parser is a mechanism for extracting new URLS from responses\n\tParser *parser.Parser\n\t// Options contains the user specified configuration options\n\tOptions *Options\n\t// ExtensionsValidator is a validator for file extensions\n\tExtensionsValidator *extensions.Validator\n\t// UniqueFilter is a filter for deduplication of unique items\n\tUniqueFilter filters.Filter\n\t// ScopeManager is a manager for validating crawling scope\n\tScopeManager *scope.Manager\n\t// Dialer is instance of the dialer for global crawler\n\tDialer *fastdialer.Dialer\n\t// Wappalyzer instance for technologies detection\n\tWappalyzer *wappalyzer.Wappalyze\n\t// DitClassifier instance for knowledge base classification\n\tDitClassifier *dit.Classifier\n\n\t// Optional structured logger for headless crawler\n\tLogger *slog.Logger\n\t// ChromeUser is the user to use for chrome\n\tChromeUser *user.User\n}\n\n// NewCrawlerOptions creates a new crawler options structure\n// from user specified options.\nfunc NewCrawlerOptions(options *Options) (*CrawlerOptions, error) {\n\toptions.ConfigureOutput()\n\textensionsValidator := extensions.NewValidator(options.ExtensionsMatch, options.ExtensionFilter, options.NoDefaultExtFilter)\n\n\tparserOptions := &parser.Options{\n\t\tAutomaticFormFill:      options.AutomaticFormFill,\n\t\tScrapeJSLuiceResponses: options.ScrapeJSLuiceResponses,\n\t\tScrapeJSResponses:      options.ScrapeJSResponses,\n\t\tDisableRedirects:       options.DisableRedirects,\n\t}\n\n\tresponseParser := parser.NewResponseParser()\n\tresponseParser.InitWithOptions(parserOptions)\n\n\tdialerOpts := fastdialer.DefaultOptions\n\tif len(options.Resolvers) > 0 {\n\t\tdialerOpts.BaseResolvers = options.Resolvers\n\t}\n\n\tfastdialerInstance, err := fastdialer.NewDialer(dialerOpts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tscopeManager, err := scope.NewManager(options.Scope, options.OutOfScope, options.FieldScope, options.NoScope)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create scope manager\")\n\t}\n\titemFilter, err := filters.NewSimple()\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create filter\")\n\t}\n\n\toutputOptions := output.Options{\n\t\tColors:                !options.NoColors,\n\t\tJSON:                  options.JSON,\n\t\tVerbose:               options.Verbose,\n\t\tStoreResponse:         options.StoreResponse,\n\t\tOutputFile:            options.OutputFile,\n\t\tFields:                options.Fields,\n\t\tStoreFields:           options.StoreFields,\n\t\tStoreResponseDir:      options.StoreResponseDir,\n\t\tNoClobber:             options.NoClobber,\n\t\tStoreFieldDir:         options.StoreFieldDir,\n\t\tOmitRaw:               options.OmitRaw,\n\t\tOmitBody:              options.OmitBody,\n\t\tFieldConfig:           options.FieldConfig,\n\t\tErrorLogFile:          options.ErrorLogFile,\n\t\tMatchRegex:            options.MatchRegex,\n\t\tFilterRegex:           options.FilterRegex,\n\t\tExtensionValidator:    extensionsValidator,\n\t\tOutputTemplate:        options.OutputTemplate,\n\t\tOutputMatchCondition:  options.OutputMatchCondition,\n\t\tOutputFilterCondition: options.OutputFilterCondition,\n\t\tExcludeOutputFields:   options.ExcludeOutputFields,\n\t\tFilterPageType:        options.FilterPageType,\n\t}\n\n\tfor _, mr := range options.OutputMatchRegex {\n\t\tcr, err := regexp.Compile(mr)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"Invalid value for match regex option\")\n\t\t}\n\t\toutputOptions.MatchRegex = append(outputOptions.MatchRegex, cr)\n\t}\n\tfor _, fr := range options.OutputFilterRegex {\n\t\tcr, err := regexp.Compile(fr)\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"Invalid value for filter regex option\")\n\t\t}\n\t\toutputOptions.FilterRegex = append(outputOptions.FilterRegex, cr)\n\t}\n\n\toutputWriter, err := output.New(outputOptions)\n\tif err != nil {\n\t\treturn nil, errkit.Wrap(err, \"could not create output writer\")\n\t}\n\n\tcrawlerOptions := &CrawlerOptions{\n\t\tExtensionsValidator: extensionsValidator,\n\t\tParser:              responseParser,\n\t\tScopeManager:        scopeManager,\n\t\tUniqueFilter:        itemFilter,\n\t\tOptions:             options,\n\t\tDialer:              fastdialerInstance,\n\t\tOutputWriter:        outputWriter,\n\t}\n\n\tif options.RateLimit > 0 {\n\t\tcrawlerOptions.RateLimit = ratelimit.New(context.Background(), uint(options.RateLimit), time.Second)\n\t} else if options.RateLimitMinute > 0 {\n\t\tcrawlerOptions.RateLimit = ratelimit.New(context.Background(), uint(options.RateLimitMinute), time.Minute)\n\t}\n\n\tif options.TechDetect {\n\t\twappalyze, err := wappalyzer.New()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcrawlerOptions.Wappalyzer = wappalyze\n\t}\n\n\tif len(options.FilterPageType) > 0 {\n\t\toptions.KnowledgeBase = true\n\t}\n\tif options.KnowledgeBase {\n\t\tclassifier, err := dit.New()\n\t\tif err != nil {\n\t\t\treturn nil, errkit.Wrap(err, \"could not init dit classifier\")\n\t\t}\n\t\tcrawlerOptions.DitClassifier = classifier\n\t}\n\n\tif options.MaxOnclickLinks <= 0 {\n\t\toptions.MaxOnclickLinks = 10\n\t}\n\n\treturn crawlerOptions, nil\n}\n\n// Close closes the crawler options resources\nfunc (c *CrawlerOptions) Close() error {\n\tc.UniqueFilter.Close()\n\treturn c.OutputWriter.Close()\n}\n\nfunc (c *CrawlerOptions) ValidatePath(path string) bool {\n\tif c.ExtensionsValidator != nil {\n\t\treturn c.ExtensionsValidator.ValidatePath(path)\n\t}\n\treturn true\n}\n\n// ClassifyPage classifies a page using the dit classifier and returns the knowledge base map.\nfunc (c *CrawlerOptions) ClassifyPage(body string) map[string]any {\n\tif c.DitClassifier == nil {\n\t\treturn nil\n\t}\n\tresult, err := c.DitClassifier.ExtractPageType(body)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tkb := map[string]any{\n\t\t\"PageType\": result.Type,\n\t}\n\tif len(result.Forms) > 0 {\n\t\tkb[\"Forms\"] = result.Forms\n\t}\n\treturn kb\n}\n\n// ValidateScope validates scope for an AbsURL\nfunc (c *CrawlerOptions) ValidateScope(absURL, rootHostname string) (bool, error) {\n\tparsed, err := urlutil.Parse(absURL)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif c.ScopeManager != nil {\n\t\treturn c.ScopeManager.Validate(parsed.URL, rootHostname)\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/types/default.go",
    "content": "package types\n\nimport \"github.com/projectdiscovery/katana/pkg/utils/queue\"\n\nvar DefaultOptions Options\n\nfunc init() {\n\tDefaultOptions = Options{\n\t\tMaxDepth:     3,\n\t\tBodyReadSize: 4 * 1024 * 1024, // 4MB\n\t\tTimeout:      10,\n\t\tTimeStable:   1,\n\t\tRetries:      1,\n\t\tStrategy:     queue.DepthFirst.String(),\n\t\tFieldScope:   \"rdn\",\n\n\t\tConcurrency: 10,\n\t\tParallelism: 10,\n\t\tRateLimit:   150,\n\t}\n}\n"
  },
  {
    "path": "pkg/types/options.go",
    "content": "package types\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/projectdiscovery/gologger\"\n\t\"github.com/projectdiscovery/gologger/levels\"\n\t\"github.com/projectdiscovery/katana/pkg/output\"\n\tfileutil \"github.com/projectdiscovery/utils/file\"\n\tlogutil \"github.com/projectdiscovery/utils/log\"\n)\n\n// OnResultCallback (output.Result)\ntype OnResultCallback func(output.Result)\n\n// OnSkipURLCallback (string)\ntype OnSkipURLCallback func(string)\n\ntype Options struct {\n\t// URLs contains a list of URLs for crawling\n\tURLs goflags.StringSlice\n\t// Resume the scan from the state stored in the resume config file\n\tResume string\n\t// Exclude host matching specified filter ('cdn', 'private-ips', cidr, ip, regex)\n\tExclude goflags.StringSlice\n\t// Scope contains a list of regexes for in-scope URLS\n\tScope goflags.StringSlice\n\t// OutOfScope contains a list of regexes for out-scope URLS\n\tOutOfScope goflags.StringSlice\n\t// NoScope disables host based default scope\n\tNoScope bool\n\t// DisplayOutScope displays out of scope items in results\n\tDisplayOutScope bool\n\t// ExtensionsMatch contains extensions to match explicitly\n\tExtensionsMatch goflags.StringSlice\n\t// ExtensionFilter contains additional items for filter list\n\tExtensionFilter goflags.StringSlice\n\t// NoDefaultExtFilter removes the default extensions from the filter list\n\tNoDefaultExtFilter bool\n\t// OutputMatchCondition is the condition to match output\n\tOutputMatchCondition string\n\t// OutputFilterCondition is the condition to filter output\n\tOutputFilterCondition string\n\t// MaxDepth is the maximum depth to crawl\n\tMaxDepth int\n\t// BodyReadSize is the maximum size of response body to read\n\tBodyReadSize int\n\t// Timeout is the time to wait for request in seconds\n\tTimeout int\n\t// TimeStable is the time to wait until the page is stable\n\tTimeStable int\n\t// CrawlDuration is the duration in seconds to crawl target from\n\tCrawlDuration time.Duration\n\t// MaxFailureCount is the maximum number of consecutive failures before stopping\n\tMaxFailureCount int\n\t// Delay is the delay between each crawl requests in seconds\n\tDelay int\n\t// RateLimit is the maximum number of requests to send per second\n\tRateLimit int\n\t// Retries is the number of retries to do for request\n\tRetries int\n\t// RateLimitMinute is the maximum number of requests to send per minute\n\tRateLimitMinute int\n\t// Concurrency is the number of concurrent crawling goroutines\n\tConcurrency int\n\t// Parallelism is the number of urls processing goroutines\n\tParallelism int\n\t// FormConfig is the path to the form configuration file\n\tFormConfig string\n\t// Proxy is the URL for the proxy server\n\tProxy string\n\t// Strategy is the crawling strategy. depth-first or breadth-first\n\tStrategy string\n\t// FieldScope is the scope field for default DNS scope\n\tFieldScope string\n\t// OutputFile is the file to write output to\n\tOutputFile string\n\t// KnownFiles enables crawling of knows files like robots.txt, sitemap.xml, etc\n\tKnownFiles string\n\t// Fields is the fields to format in output\n\tFields string\n\t// StoreFields is the fields to store in separate per-host files\n\tStoreFields string\n\t// FieldConfig is the path to the custom field configuration file\n\tFieldConfig string\n\t// NoColors disables coloring of response output\n\tNoColors bool\n\t// JSON enables writing output in JSON format\n\tJSON bool\n\t// ExcludeOutputFields is the list of fields to exclude from the output\n\tExcludeOutputFields goflags.StringSlice\n\t// ListOutputFields is the list of fields\n\tListOutputFields bool\n\t// Silent shows only output\n\tSilent bool\n\t// Verbose specifies showing verbose output\n\tVerbose bool\n\t// TechDetect enables technology detection\n\tTechDetect bool\n\t// EnableDiagnostics enables diagnostics\n\tEnableDiagnostics bool\n\t// Version enables showing of crawler version\n\tVersion bool\n\t// ScrapeJSResponses enables scraping of relative endpoints from javascript\n\tScrapeJSResponses bool\n\t// ScrapeJSLuiceResponses enables scraping of endpoints from javascript using jsluice\n\tScrapeJSLuiceResponses bool\n\t// CustomHeaders is a list of custom headers to add to request\n\tCustomHeaders goflags.StringSlice\n\t// Headless enables headless scraping\n\tHeadless bool\n\t// HeadlessHybrid enables headless hybrid scraping\n\tHeadlessHybrid bool\n\t// AutomaticFormFill enables optional automatic form filling and submission\n\tAutomaticFormFill bool\n\t// FormExtraction enables extraction of form, input, textarea & select elements\n\tFormExtraction bool\n\t// UseInstalledChrome skips chrome install and use local instance\n\tUseInstalledChrome bool\n\t// ShowBrowser specifies whether the show the browser in headless mode\n\tShowBrowser bool\n\t// HeadlessOptionalArguments specifies optional arguments to pass to Chrome\n\tHeadlessOptionalArguments goflags.StringSlice\n\t// HeadlessNoSandbox specifies if chrome should be start in --no-sandbox mode\n\tHeadlessNoSandbox bool\n\t// SystemChromePath : Specify the chrome binary path for headless crawling\n\tSystemChromePath string\n\t// ChromeWSUrl : Specify the Chrome debugger websocket url for a running Chrome instance to attach to\n\tChromeWSUrl string\n\t// OnResult allows callback function on a result\n\tOnResult OnResultCallback\n\t// OnSkipURL allows callback function on a skipped url\n\tOnSkipURL OnSkipURLCallback\n\t// StoreResponse specifies if katana should store http requests/responses\n\tStoreResponse bool\n\t// StoreResponseDir specifies if katana should use a custom directory to store http requests/responses\n\tStoreResponseDir string\n\t// NoClobber specifies if katana should overwrite existing output files\n\tNoClobber bool\n\t// StoreFieldDir specifies if katana should use a custom directory to store fields\n\tStoreFieldDir string\n\t// OmitRaw omits raw requests/responses from the output\n\tOmitRaw bool\n\t// OmitBody omits the response body from the output\n\tOmitBody bool\n\t// ChromeDataDir : \tSpecify the --user-data-dir to chrome binary to preserve sessions\n\tChromeDataDir string\n\t// HeadlessNoIncognito specifies if chrome should be started without incognito mode\n\tHeadlessNoIncognito bool\n\t// XhrExtraction extract xhr requests\n\tXhrExtraction bool\n\t// HealthCheck determines if a self-healthcheck should be performed\n\tHealthCheck bool\n\t// PprofServer enables pprof server\n\tPprofServer bool\n\t// ErrorLogFile specifies a file to write with the errors of all requests\n\tErrorLogFile string\n\t// Resolvers contains custom resolvers\n\tResolvers goflags.StringSlice\n\t// OutputTemplate enables custom output template\n\tOutputTemplate string\n\t// OutputMatchRegex is the regex to match output url\n\tOutputMatchRegex goflags.StringSlice\n\t// OutputFilterRegex is the regex to filter output url\n\tOutputFilterRegex goflags.StringSlice\n\t// FilterRegex is the slice regex to filter url\n\tFilterRegex []*regexp.Regexp\n\t// MatchRegex is the slice regex to match url\n\tMatchRegex []*regexp.Regexp\n\t//DisableUpdateCheck disables automatic update check\n\tDisableUpdateCheck bool\n\t//IgnoreQueryParams ignore crawling same path with different query-param values\n\tIgnoreQueryParams bool\n\t// FilterSimilar filters crawling of similar looking URLs\n\t// by normalizing variable path segments (IDs, UUIDs, hashes, dates)\n\tFilterSimilar bool\n\t// FilterSimilarThreshold is the number of distinct values at a path position\n\t// before it is treated as a parameter (default 10, lower = more aggressive)\n\tFilterSimilarThreshold int\n\t// Debug\n\tDebug bool\n\t// TlsImpersonate enables experimental tls ClientHello randomization for standard crawler\n\tTlsImpersonate bool\n\t// DisableRedirects disables the following of redirects\n\tDisableRedirects bool\n\t// PathClimb enables path expansion (auto crawl discovered paths)\n\tPathClimb bool\n\t// DisableUniqueFilter disables duplicate content filtering\n\tDisableUniqueFilter bool\n\t// MaxOnclickLinks is the maximum number of onclick links to process per page (default: 10)\n\tMaxOnclickLinks       int\n\tCaptchaSolverProvider string\n\tCaptchaSolverAPIKey   string\n\t// KnowledgeBase enables knowledge base classification using dit\n\tKnowledgeBase bool\n\t// FilterPageType filters results by page type\n\tFilterPageType goflags.StringSlice\n}\n\nfunc (options *Options) ParseCustomHeaders() map[string]string {\n\tcustomHeaders := make(map[string]string)\n\tfor _, v := range options.CustomHeaders {\n\t\tif headerParts := strings.SplitN(v, \":\", 2); len(headerParts) >= 2 {\n\t\t\tcustomHeaders[strings.Trim(headerParts[0], \" \")] = strings.Trim(headerParts[1], \" \")\n\t\t}\n\t}\n\treturn customHeaders\n}\n\nfunc (options *Options) ParseHeadlessOptionalArguments() map[string]string {\n\tvar (\n\t\tlastKey           string\n\t\toptionalArguments = make(map[string]string)\n\t)\n\tfor _, v := range options.HeadlessOptionalArguments {\n\t\tif v == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif argParts := strings.SplitN(v, \"=\", 2); len(argParts) >= 2 {\n\t\t\tkey := strings.TrimSpace(argParts[0])\n\t\t\tvalue := strings.TrimSpace(argParts[1])\n\t\t\tif key != \"\" && value != \"\" {\n\t\t\t\toptionalArguments[key] = value\n\t\t\t\tlastKey = key\n\t\t\t}\n\t\t} else if !strings.HasPrefix(v, \"--\") {\n\t\t\toptionalArguments[lastKey] += \",\" + v\n\t\t} else {\n\t\t\toptionalArguments[v] = \"\"\n\t\t}\n\t}\n\treturn optionalArguments\n}\n\nfunc (options *Options) ShouldResume() bool {\n\treturn options.Resume != \"\" && fileutil.FileExists(options.Resume)\n}\n\n// ConfigureOutput configures the output logging levels to be displayed on the screen\nfunc (options *Options) ConfigureOutput() {\n\tif options.Silent {\n\t\tgologger.DefaultLogger.SetMaxLevel(levels.LevelSilent)\n\t} else if options.Verbose {\n\t\tgologger.DefaultLogger.SetMaxLevel(levels.LevelWarning)\n\t} else if options.Debug {\n\t\tgologger.DefaultLogger.SetMaxLevel(levels.LevelDebug)\n\t} else {\n\t\tgologger.DefaultLogger.SetMaxLevel(levels.LevelInfo)\n\t}\n\n\tlogutil.DisableDefaultLogger()\n}\n"
  },
  {
    "path": "pkg/types/options_test.go",
    "content": "package types\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/projectdiscovery/goflags\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseCustomHeaders(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  map[string]string\n\t}{\n\t\t{\n\t\t\tname:  \"single value\",\n\t\t\tinput: \"a:b\",\n\t\t\twant:  map[string]string{\"a\": \"b\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"empty string\",\n\t\t\tinput: \"\",\n\t\t\twant:  map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"empty value\",\n\t\t\tinput: \"a:\",\n\t\t\twant:  map[string]string{\"a\": \"\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"double input\",\n\t\t\tinput: \"a:b,c:d\",\n\t\t\twant:  map[string]string{\"a\": \"b\", \"c\": \"d\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstrsl := goflags.StringSlice{}\n\t\t\tfor _, v := range strings.Split(tt.input, \",\") {\n\t\t\t\t//nolint\n\t\t\t\tstrsl.Set(v)\n\t\t\t}\n\t\t\topt := Options{CustomHeaders: strsl}\n\t\t\tgot := opt.ParseCustomHeaders()\n\t\t\trequire.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n\nfunc TestParseHeadlessOptionalArguments(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tinput string\n\t\twant  map[string]string\n\t}{\n\t\t{\n\t\t\tname:  \"single value\",\n\t\t\tinput: \"a=b\",\n\t\t\twant:  map[string]string{\"a\": \"b\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"empty string\",\n\t\t\tinput: \"\",\n\t\t\twant:  map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"empty key\",\n\t\t\tinput: \"=b\",\n\t\t\twant:  map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"empty value\",\n\t\t\tinput: \"a=\",\n\t\t\twant:  map[string]string{},\n\t\t},\n\t\t{\n\t\t\tname:  \"double input\",\n\t\t\tinput: \"a=b,c=d\",\n\t\t\twant:  map[string]string{\"a\": \"b\", \"c\": \"d\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"duplicated input\",\n\t\t\tinput: \"a=b,a=b\",\n\t\t\twant:  map[string]string{\"a\": \"b\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"values with dash with boolean flag at the end\",\n\t\t\tinput: \"--a=a/b,c/d--z--n--m/a,--c=k,--h\",\n\t\t\twant:  map[string]string{\"--a\": \"a/b,c/d--z--n--m/a\", \"--c\": \"k\", \"--h\": \"\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"values with dash boolean flag at the beginning\",\n\t\t\tinput: \"--h,--a=a/b,c/d--z--n--m/a,--c=k\",\n\t\t\twant:  map[string]string{\"--h\": \"\", \"--a\": \"a/b,c/d--z--n--m/a\", \"--c\": \"k\"},\n\t\t},\n\t\t{\n\t\t\tname:  \"values with dash boolean flag in the middle\",\n\t\t\tinput: \"--a=a/b,c/d--z--n--m/a,--h,--c=k\",\n\t\t\twant:  map[string]string{\"--a\": \"a/b,c/d--z--n--m/a\", \"--h\": \"\", \"--c\": \"k\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tstrsl := goflags.StringSlice{}\n\t\t\tfor _, v := range strings.Split(tt.input, \",\") {\n\t\t\t\t//nolint\n\t\t\t\tstrsl.Set(v)\n\t\t\t}\n\t\t\topt := Options{HeadlessOptionalArguments: strsl}\n\t\t\tgot := opt.ParseHeadlessOptionalArguments()\n\t\t\trequire.Equal(t, tt.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/extensions/extensions.go",
    "content": "package extensions\n\nimport (\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/projectdiscovery/gologger\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// defaultDenylist is the default list of extensions to be denied\nvar defaultExtFilter = []string{\".3g2\", \".3gp\", \".7z\", \".apk\", \".arj\", \".avi\", \".axd\", \".bmp\", \".csv\", \".deb\", \".dll\", \".doc\", \".drv\", \".eot\", \".exe\", \".flv\", \".gif\", \".gifv\", \".gz\", \".h264\", \".ico\", \".iso\", \".jar\", \".jpeg\", \".jpg\", \".lock\", \".m4a\", \".m4v\", \".map\", \".mkv\", \".mov\", \".mp3\", \".mp4\", \".mpeg\", \".mpg\", \".msi\", \".ogg\", \".ogm\", \".ogv\", \".otf\", \".pdf\", \".pkg\", \".png\", \".ppt\", \".psd\", \".rar\", \".rm\", \".rpm\", \".svg\", \".swf\", \".sys\", \".tar.gz\", \".tar\", \".tif\", \".tiff\", \".ttf\", \".txt\", \".vob\", \".wav\", \".webm\", \".webp\", \".wmv\", \".woff\", \".woff2\", \".xcf\", \".xls\", \".xlsx\", \".zip\"}\n\n// Validator is a validator for file extension\ntype Validator struct {\n\textensionsMatch  map[string]struct{}\n\textensionsFilter map[string]struct{}\n}\n\n// NewValidator creates a new extension validator instance\nfunc NewValidator(extensionsMatch, extensionsFilter []string, noDefaultExtFilter bool) *Validator {\n\tvalidator := &Validator{\n\t\textensionsMatch:  make(map[string]struct{}),\n\t\textensionsFilter: make(map[string]struct{}),\n\t}\n\n\tfor _, extension := range extensionsMatch {\n\t\tvalidator.extensionsMatch[normalizeExtension(extension)] = struct{}{}\n\t}\n\tif !noDefaultExtFilter {\n\t\tfor _, item := range defaultExtFilter {\n\t\t\tvalidator.extensionsFilter[normalizeExtension(item)] = struct{}{}\n\t\t}\n\t}\n\tfor _, extension := range extensionsFilter {\n\t\tvalidator.extensionsFilter[normalizeExtension(extension)] = struct{}{}\n\t}\n\treturn validator\n}\n\n// ValidatePath returns true if an extension is allowed by the validator\nfunc (e *Validator) ValidatePath(item string) bool {\n\tvar extension string\n\tu, err := urlutil.Parse(item)\n\tif err != nil {\n\t\tgologger.Warning().Msgf(\"validatepath: failed to parse url %v got %v\", item, err)\n\t\treturn false\n\t}\n\tif u.Path != \"\" {\n\t\textension = strings.ToLower(path.Ext(u.Path))\n\t} else {\n\t\textension = strings.ToLower(path.Ext(item))\n\t}\n\tif extension == \"\" && len(e.extensionsMatch) > 0 {\n\t\treturn false\n\t}\n\tif len(e.extensionsMatch) > 0 {\n\t\tif _, ok := e.extensionsMatch[extension]; ok {\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tif _, ok := e.extensionsFilter[extension]; ok {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc normalizeExtension(extension string) string {\n\textension = strings.ToLower(extension)\n\tif strings.HasPrefix(extension, \".\") {\n\t\treturn extension\n\t}\n\treturn \".\" + extension\n}\n"
  },
  {
    "path": "pkg/utils/extensions/extensions_test.go",
    "content": "package extensions\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestValidatorValidate(t *testing.T) {\n\tvalidator := NewValidator([]string{\".go\"}, nil, false)\n\trequire.True(t, validator.ValidatePath(\"main.go\"), \"could not validate correct data with extensions\")\n\trequire.False(t, validator.ValidatePath(\"main.php\"), \"could not validate correct data with wrong extension\")\n\n\tvalidator = NewValidator(nil, []string{\".php\"}, false)\n\trequire.False(t, validator.ValidatePath(\"main.php\"), \"could not validate correct data with deny list extension\")\n\trequire.True(t, validator.ValidatePath(\"main.go\"), \"could not validate correct data with no custom extensions\")\n\n\tvalidator = NewValidator([]string{\"png\"}, nil, false)\n\trequire.True(t, validator.ValidatePath(\"main.png\"), \"could not validate correct data with default denylist bypass\")\n\n\tvalidator = NewValidator(nil, nil, true)\n\trequire.True(t, validator.ValidatePath(\"main.png\"), \"could not validate correct data with no default extension filter\")\n\n\tvalidator = NewValidator(nil, []string{\"png\"}, true)\n\trequire.False(t, validator.ValidatePath(\"main.png\"), \"could not validate correct data with no default extension filter and custom filter\")\n}\n"
  },
  {
    "path": "pkg/utils/filters/filters.go",
    "content": "package filters\n\n// Filter is an interface implemented by deduplication mechanism\ntype Filter interface {\n\t// Close closes the filter and releases associated resources\n\tClose()\n\t// UniqueURL specifies whether a URL is unique\n\tUniqueURL(url string) bool\n\t// UniqueContent specifies whether a content is unique\n\t// Deduplication is done by hashing of the response data.\n\t//\n\t// TODO: Consider levenshtein length / keyword based hashing\n\t// to account for dynamic response content.\n\tUniqueContent(content []byte) bool\n\t// IsCycle attempts to detect if the current URL is a cycle\n\t// until graph navigation is implemented, the only ways to discard a potential\n\t// loop cycle are\n\t// - implementing upper hard limit to the URL length (https://bugs.chromium.org/p/chromium/issues/detail?id=69227 => 2Mb)\n\t// - Heuristically find the longest repeating substring and set a max threshold of how many max times it should repeat (eg. 10)\n\t// Todo: This should be replace with graph cycle detection => https://github.com/projectdiscovery/katana/pull/174\n\tIsCycle(url string) bool\n}\n"
  },
  {
    "path": "pkg/utils/filters/filters_test.go",
    "content": "package filters\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSimpleFilter(t *testing.T) {\n\tsimple, err := NewSimple()\n\trequire.NoError(t, err, \"could not create filter\")\n\tdefer simple.Close()\n\n\tunique := simple.UniqueURL(\"https://example.com\")\n\trequire.True(t, unique, \"could not get unique value\")\n\n\tunique = simple.UniqueURL(\"https://example.com\")\n\trequire.False(t, unique, \"could get unique value\")\n}\n"
  },
  {
    "path": "pkg/utils/filters/simple.go",
    "content": "package filters\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\n\t\"github.com/projectdiscovery/hmap/store/hybrid\"\n\tstringsutil \"github.com/projectdiscovery/utils/strings\"\n)\n\nconst (\n\t// TODO: this should be lowered to a reasonable amount (eg: 1024-2048-4096)\n\tMaxChromeURLLength = 2097152\n\t// TODO: fine tune the number\n\tMinSequenceLength = 10\n\tMaxSequenceCount  = 10\n)\n\n// Simple is a simple unique URL filter.\n//\n// The URLs are maintained in a global sync.Map for\n// deduplication and no normalization is performed.\ntype Simple struct {\n\tdata *hybrid.HybridMap\n}\n\n// NewSimple returns a new simple filter\nfunc NewSimple() (*Simple, error) {\n\thmap, err := hybrid.New(hybrid.DefaultDiskOptions)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Simple{data: hmap}, nil\n}\n\n// UniqueURL returns true if the URL is unique\nfunc (s *Simple) UniqueURL(url string) bool {\n\t_, found := s.data.Get(url)\n\tif found {\n\t\treturn false\n\t}\n\t_ = s.data.Set(url, nil)\n\treturn true\n}\n\n// UniqueContent returns true if the content is unique\nfunc (s *Simple) UniqueContent(data []byte) bool {\n\thash := md5.Sum([]byte(data))\n\tencoded := hex.EncodeToString(hash[:])\n\n\t_, found := s.data.Get(encoded)\n\tif found {\n\t\treturn false\n\t}\n\t_ = s.data.Set(encoded, nil)\n\treturn true\n}\n\n// Close closes the filter and releases associated resources\nfunc (s *Simple) Close() {\n\t_ = s.data.Close()\n}\n\n// IsCycle attempts to determine if the url is a cycle loop\nfunc (s *Simple) IsCycle(url string) bool {\n\tif len(url) > MaxChromeURLLength {\n\t\treturn true\n\t}\n\n\tif sequence := stringsutil.LongestRepeatingSequence(url); sequence.Count >= MaxSequenceCount && len(sequence.Sequence) > MinSequenceLength {\n\t\treturn true\n\t}\n\n\treturn false\n}\n"
  },
  {
    "path": "pkg/utils/formfields.go",
    "content": "package utils\r\n\r\nimport (\r\n\t\"strings\"\r\n\r\n\t\"github.com/projectdiscovery/katana/pkg/navigation\"\r\n\r\n\t\"github.com/PuerkitoBio/goquery\"\r\n\t\"github.com/projectdiscovery/utils/generic\"\r\n\turlutil \"github.com/projectdiscovery/utils/url\"\r\n)\r\n\r\n// parses form, input, textarea & select elements\r\nfunc ParseFormFields(document *goquery.Document) []navigation.Form {\r\n\tvar forms []navigation.Form\r\n\r\n\tdocument.Find(\"form\").Each(func(i int, formElem *goquery.Selection) {\r\n\t\tform := navigation.Form{}\r\n\r\n\t\taction, _ := formElem.Attr(\"action\")\r\n\t\tmethod, _ := formElem.Attr(\"method\")\r\n\t\tenctype, _ := formElem.Attr(\"enctype\")\r\n\r\n\t\tif method == \"\" {\r\n\t\t\tmethod = \"GET\"\r\n\t\t}\r\n\r\n\t\tif enctype == \"\" && method != \"GET\" {\r\n\t\t\tenctype = \"application/x-www-form-urlencoded\"\r\n\t\t}\r\n\r\n\t\tif actionUrl, err := urlutil.ParseURL(action, true); err == nil {\r\n\t\t\t// do not modify absolute urls and windows paths\r\n\t\t\tif actionUrl.IsAbs() || strings.HasPrefix(action, \"//\") || strings.HasPrefix(action, \"\\\\\") {\r\n\t\t\t\t// keep absolute urls as is\r\n\t\t\t\t_ = action\r\n\t\t\t} else if document.Url != nil {\r\n\t\t\t\t// concatenate relative urls with base url\r\n\t\t\t\t// clone base url\r\n\t\t\t\tcloned, err := urlutil.ParseURL(document.Url.String(), true)\r\n\t\t\t\tif err != nil {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif strings.HasPrefix(action, \"/\") {\r\n\t\t\t\t\t// relative path\r\n\t\t\t\t\t// \t<form action=/root_rel></form> => https://example.com/root_rel\r\n\t\t\t\t\t_ = cloned.UpdateRelPath(action, true)\r\n\t\t\t\t\taction = cloned.String()\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// \t<form action=path_rel></form> => https://example.com/path/path_rel\r\n\t\t\t\t\tif err := cloned.MergePath(action, false); err != nil {\r\n\t\t\t\t\t\treturn\r\n\t\t\t\t\t}\r\n\t\t\t\t\taction = cloned.String()\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\taction = document.Url.String()\r\n\t\t}\r\n\r\n\t\tform.Method = strings.ToUpper(method)\r\n\t\tform.Action = action\r\n\t\tform.Enctype = enctype\r\n\r\n\t\tformElem.Find(\"input, textarea, select\").Each(func(i int, inputElem *goquery.Selection) {\r\n\t\t\tname, ok := inputElem.Attr(\"name\")\r\n\t\t\tif !ok {\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\r\n\t\t\tform.Parameters = append(form.Parameters, name)\r\n\t\t})\r\n\r\n\t\tif !generic.EqualsAll(\"\", form.Action, form.Method, form.Enctype) || len(form.Parameters) > 0 {\r\n\t\t\tforms = append(forms, form)\r\n\t\t}\r\n\t})\r\n\r\n\treturn forms\r\n}\r\n"
  },
  {
    "path": "pkg/utils/formfields_test.go",
    "content": "package utils\r\n\r\nimport (\r\n\t\"net/url\"\r\n\t\"strings\"\r\n\t\"testing\"\r\n\r\n\t\"github.com/PuerkitoBio/goquery\"\r\n\t\"github.com/stretchr/testify/require\"\r\n)\r\n\r\nvar htmlFormExample = `<html>\r\n<head>\r\n\t<title>HTML Form Test</title>\r\n</head>\r\n<body>\r\n\t<form method=\"POST\" action=\"/test\">  \r\n\t\t<input type=\"text\" name=\"firstname\"><br> \r\n\t\t<textarea name=textarea1></textarea> \r\n\t\t<select name=select1></select> \r\n\t\t<input type=text /> \r\n\t</form>  \r\n\t<form method=post action=https://abs.example.com></form>\r\n\t<form action=//prel.example.com></form>\r\n\t<form action=\\\\unc.example.com></form>\r\n\t<form action=/root_rel></form>\r\n\t<form action=rel_path></form>\r\n\t<form></form>\r\n</body>\r\n</html>`\r\n\r\nfunc TestParseFormFields(t *testing.T) {\r\n\tdocument, err := goquery.NewDocumentFromReader(strings.NewReader(htmlFormExample))\r\n\trequire.NoError(t, err, \"could not read document\")\r\n\r\n\tdocument.Url, _ = url.Parse(\"https://example.com/path\")\r\n\tforms := ParseFormFields(document)\r\n\r\n\trequire.Equal(t, \"https://example.com/test\", forms[0].Action)\r\n\trequire.Equal(t, \"POST\", forms[0].Method)\r\n\trequire.Equal(t, \"POST\", forms[1].Method)\r\n\trequire.Equal(t, \"https://abs.example.com\", forms[1].Action)\r\n\trequire.Equal(t, \"GET\", forms[2].Method)\r\n\trequire.Equal(t, \"//prel.example.com\", forms[2].Action)\r\n\trequire.Equal(t, \"GET\", forms[3].Method)\r\n\trequire.Equal(t, \"\\\\\\\\unc.example.com\", forms[3].Action)\r\n\trequire.Equal(t, \"GET\", forms[4].Method)\r\n\trequire.Equal(t, \"https://example.com/root_rel\", forms[4].Action)\r\n\trequire.Equal(t, \"GET\", forms[5].Method)\r\n\trequire.Equal(t, \"https://example.com/path/rel_path\", forms[5].Action)\r\n\trequire.Equal(t, \"GET\", forms[6].Method)\r\n\trequire.Equal(t, \"https://example.com/path\", forms[6].Action)\r\n\trequire.Contains(t, forms[0].Parameters, \"firstname\")\r\n\trequire.Contains(t, forms[0].Parameters, \"textarea1\")\r\n\trequire.Contains(t, forms[0].Parameters, \"select1\")\r\n\trequire.Equal(t, 3, len(forms[0].Parameters), \"found more or less parameters than where present\")\r\n\trequire.Equal(t, 7, len(forms), \"found more or less forms than where present\")\r\n}\r\n"
  },
  {
    "path": "pkg/utils/formfill.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"github.com/rs/xid\"\n)\n\n// FormData is the global form fill data instance\nvar FormData FormFillData\n\nfunc init() {\n\tFormData = DefaultFormFillData\n}\n\n// FormFillData contains suggestions for form filling\ntype FormFillData struct {\n\tEmail       string `yaml:\"email\"`\n\tColor       string `yaml:\"color\"`\n\tPassword    string `yaml:\"password\"`\n\tPhoneNumber string `yaml:\"phone\"`\n\tPlaceholder string `yaml:\"placeholder\"`\n}\n\nvar DefaultFormFillData = FormFillData{\n\tEmail:       fmt.Sprintf(\"%s@example.org\", xid.New().String()),\n\tColor:       \"#e66465\",\n\tPassword:    \"katanaP@assw0rd1\",\n\tPhoneNumber: \"2124567890\",\n\tPlaceholder: \"katana\",\n}\n\n// FormInput is an input for a form field\ntype FormInput struct {\n\tType       string\n\tName       string\n\tValue      string\n\tAttributes mapsutil.OrderedMap[string, string]\n}\n\n// SelectOption is an option for a select input\ntype SelectOption struct {\n\tValue      string\n\tSelected   string\n\tAttributes mapsutil.OrderedMap[string, string]\n}\n\n// FormSelect is a select input for a form field\ntype FormSelect struct {\n\tName          string\n\tAttributes    mapsutil.OrderedMap[string, string]\n\tSelectOptions []SelectOption\n}\n\ntype FormTextArea struct {\n\tName       string\n\tAttributes mapsutil.OrderedMap[string, string]\n}\n\n// FormInputFillSuggestions returns a list of form filling suggestions\n// for inputs returning the specified recommended values.\nfunc FormInputFillSuggestions(inputs []FormInput) mapsutil.OrderedMap[string, string] {\n\tdata := mapsutil.NewOrderedMap[string, string]()\n\n\t// Fill checkboxes and radioboxes first or default values first\n\tfor i, input := range inputs {\n\t\tswitch input.Type {\n\t\tcase \"radio\":\n\t\t\t// Use a single radio name per value\n\t\t\tif !data.Has(input.Name) {\n\t\t\t\tdata.Set(input.Name, input.Value)\n\t\t\t}\n\t\tcase \"checkbox\":\n\t\t\tdata.Set(input.Name, input.Value)\n\n\t\tdefault:\n\t\t\t// If there is a value, use it for the input. Else\n\t\t\t// infer the values based on input types.\n\t\t\tif input.Value != \"\" {\n\t\t\t\tdata.Set(input.Name, input.Value)\n\t\t\t} else if value, ok := input.Attributes.Get(\"placeholder\"); ok {\n\t\t\t\tinputs[i].Value = value\n\t\t\t\tdata.Set(input.Name, value)\n\t\t\t}\n\t\t}\n\t}\n\n\t// getIntWithdefault returns the integer value of the key or default value\n\tgetIntWithdefault := func(input *FormInput, key string, defaultValue int) int {\n\t\tif value, ok := input.Attributes.Get(key); ok {\n\t\t\tif intValue, err := strconv.Atoi(value); err == nil {\n\t\t\t\treturn intValue\n\t\t\t}\n\t\t}\n\t\treturn defaultValue\n\t}\n\n\t// Fill rest of the inputs based on their types or name and ids\n\tfor _, input := range inputs {\n\t\tif input.Value != \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tswitch input.Type {\n\t\tcase \"email\":\n\t\t\tdata.Set(input.Name, FormData.Email)\n\t\tcase \"color\":\n\t\t\tdata.Set(input.Name, FormData.Color)\n\t\tcase \"number\", \"range\":\n\t\t\tmin := getIntWithdefault(&input, \"min\", 1)\n\t\t\tmax := getIntWithdefault(&input, \"max\", 10)\n\t\t\tstep := getIntWithdefault(&input, \"step\", 1)\n\t\t\tval := min + step\n\t\t\tif val > max {\n\t\t\t\tval = max - step\n\t\t\t}\n\t\t\tdata.Set(input.Name, strconv.Itoa(val))\n\t\tcase \"password\":\n\t\t\tdata.Set(input.Name, FormData.Password)\n\t\tcase \"tel\":\n\t\t\tdata.Set(input.Name, FormData.Password)\n\t\tdefault:\n\t\t\tdata.Set(input.Name, FormData.Placeholder)\n\t\t}\n\t}\n\treturn data\n}\n\n// FormSelectFill fills a map with selected values from a slice of FormSelect structs.\n// It iterates over each FormSelect struct in the inputs slice and checks for a selected option.\n// If a selected option is found, it adds the corresponding value to the map using the input's name as the key.\n// If no option is selected, it selects the first option and adds its value to the map.\n// The function returns the filled map.\nfunc FormSelectFill(inputs []FormSelect) mapsutil.OrderedMap[string, string] {\n\tdata := mapsutil.NewOrderedMap[string, string]()\n\tfor _, input := range inputs {\n\t\tfor _, option := range input.SelectOptions {\n\t\t\tif option.Selected != \"\" {\n\t\t\t\tdata.Set(input.Name, option.Value)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\t// If no option is selected, select the first one\n\t\tif !data.Has(input.Name) && len(input.SelectOptions) > 0 {\n\t\t\tdata.Set(input.Name, input.SelectOptions[0].Value)\n\t\t}\n\t}\n\treturn data\n}\n\n// FormTextAreaFill fills the form text areas with placeholder values.\n// It takes a slice of FormTextArea structs as input and returns an OrderedMap\n// containing the form field names as keys and the placeholder values as values.\nfunc FormTextAreaFill(inputs []FormTextArea) mapsutil.OrderedMap[string, string] {\n\tdata := mapsutil.NewOrderedMap[string, string]()\n\tfor _, input := range inputs {\n\t\tdata.Set(input.Name, FormData.Placeholder)\n\t}\n\treturn data\n}\n\n// FormFillSuggestions takes a slice of form fields and returns an ordered map\n// containing suggestions for filling those form fields. The function iterates\n// over each form field and based on its type, calls the corresponding fill\n// function to generate suggestions. The suggestions are then merged into a\n// single ordered map and returned.\n//\n// Parameters:\n// - formFields: A slice of form fields.\n//\n// Returns:\n// An ordered map containing suggestions for filling the form fields.\nfunc FormFillSuggestions(formFields []interface{}) mapsutil.OrderedMap[string, string] {\n\tmerged := mapsutil.NewOrderedMap[string, string]()\n\tfor _, item := range formFields {\n\t\tswitch v := item.(type) {\n\t\tcase FormInput:\n\t\t\tdataMapInputs := FormInputFillSuggestions([]FormInput{v})\n\t\t\tMergeDataMaps(&merged, dataMapInputs)\n\t\tcase FormSelect:\n\t\t\tdataMapSelects := FormSelectFill([]FormSelect{v})\n\t\t\tMergeDataMaps(&merged, dataMapSelects)\n\n\t\tcase FormTextArea:\n\t\t\tdataMapTextArea := FormTextAreaFill([]FormTextArea{v})\n\t\t\tMergeDataMaps(&merged, dataMapTextArea)\n\t\t}\n\t}\n\treturn merged\n}\n\n// ConvertGoquerySelectionToFormInput converts goquery selection to form input\nfunc ConvertGoquerySelectionToFormInput(item *goquery.Selection) FormInput {\n\tattrs := item.Nodes[0].Attr\n\tinput := FormInput{Attributes: mapsutil.NewOrderedMap[string, string]()}\n\n\tfor _, attribute := range attrs {\n\t\tswitch attribute.Key {\n\t\tcase \"name\":\n\t\t\tinput.Name = attribute.Val\n\t\tcase \"value\":\n\t\t\tinput.Value = attribute.Val\n\t\tcase \"type\":\n\t\t\tinput.Type = attribute.Val\n\t\tdefault:\n\t\t\tinput.Attributes.Set(attribute.Key, attribute.Val)\n\t\t}\n\t}\n\treturn input\n}\n\n// ConvertGoquerySelectionToSelectOption converts a goquery.Selection object to a SelectOption object.\n// It extracts the attributes from the goquery.Selection object and populates a SelectOption object with the extracted values.\nfunc ConvertGoquerySelectionToSelectOption(item *goquery.Selection) SelectOption {\n\tattrs := item.Nodes[0].Attr\n\tinput := SelectOption{Attributes: mapsutil.NewOrderedMap[string, string]()}\n\tfor _, attribute := range attrs {\n\t\tswitch attribute.Key {\n\t\tcase \"value\":\n\t\t\tinput.Value = attribute.Val\n\n\t\tcase \"selected\":\n\t\t\tinput.Selected = attribute.Key\n\t\tdefault:\n\t\t\tinput.Attributes.Set(attribute.Key, attribute.Val)\n\t\t}\n\t}\n\treturn input\n}\n\n// ConvertGoquerySelectionToFormSelect converts a goquery.Selection object to a FormSelect object.\n// It extracts the attributes and form options from the goquery.Selection and populates them in the FormSelect object.\n// The converted FormSelect object is then returned.\nfunc ConvertGoquerySelectionToFormSelect(item *goquery.Selection) FormSelect {\n\tattrs := item.Nodes[0].Attr\n\tinput := FormSelect{Attributes: mapsutil.NewOrderedMap[string, string]()}\n\tfor _, attribute := range attrs {\n\t\tswitch attribute.Key {\n\t\tcase \"name\":\n\t\t\tinput.Name = attribute.Val\n\t\tdefault:\n\t\t\tinput.Attributes.Set(attribute.Key, attribute.Val)\n\t\t}\n\t}\n\n\tinput.SelectOptions = []SelectOption{}\n\titem.Find(\"option\").Each(func(_ int, option *goquery.Selection) {\n\t\tinput.SelectOptions = append(input.SelectOptions, ConvertGoquerySelectionToSelectOption(option))\n\t})\n\treturn input\n}\n\n// ConvertGoquerySelectionToFormTextArea converts a goquery.Selection object to a FormTextArea struct.\n// It extracts the attributes from the first node of the selection and populates a FormTextArea object with the extracted data.\n// The \"name\" attribute is assigned to the Name field of the FormTextArea, while other attributes are added to the Attributes map.\nfunc ConvertGoquerySelectionToFormTextArea(item *goquery.Selection) FormTextArea {\n\tattrs := item.Nodes[0].Attr\n\tinput := FormTextArea{Attributes: mapsutil.NewOrderedMap[string, string]()}\n\tfor _, attribute := range attrs {\n\t\tswitch attribute.Key {\n\t\tcase \"name\":\n\t\t\tinput.Name = attribute.Val\n\t\tdefault:\n\t\t\tinput.Attributes.Set(attribute.Key, attribute.Val)\n\t\t}\n\t}\n\treturn input\n}\n\n// ConvertGoquerySelectionToFormField converts a goquery.Selection object to a form field.\n// It checks the type of the selection and calls the appropriate conversion function.\n// If the selection is an input, it calls ConvertGoquerySelectionToFormInput.\n// If the selection is a select, it calls ConvertGoquerySelectionToFormSelect.\n// If the selection is a textarea, it calls ConvertGoquerySelectionToFormTextArea.\n// If the selection is of any other type, it returns nil.\nfunc ConvertGoquerySelectionToFormField(item *goquery.Selection) interface{} {\n\tif item.Is(\"input\") {\n\t\treturn ConvertGoquerySelectionToFormInput(item)\n\t}\n\tif item.Is(\"select\") {\n\t\treturn ConvertGoquerySelectionToFormSelect(item)\n\t}\n\tif item.Is(\"textarea\") {\n\t\treturn ConvertGoquerySelectionToFormTextArea(item)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/utils/formfill_test.go",
    "content": "package utils\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nvar htmlFormInputExample = `<html>\n<head>\n\t<title>HTML Form Test</title>\n</head>\n<body>\n\t<form action=\"/\">  \n\t\t<label>User id: </label>  \n\t\t<input type=\"text\" name=\"firstname\"><br>  \n\t\t<label>Password: </label>  \n\t\t<input type=\"Password\" name=\"password\"><br> \n\t\t<p>Kindly Select your favorite color</p>  \n\t\t<input type=\"radio\" name=\"color\" value=\"red\"> Red <br>  \n\t\t<input type=\"radio\" name=\"color\" value=\"blue\"> blue <br>  \n\t\t<input type=\"radio\" name=\"color\" value=\"green\">green <br>   \n\t\t<p>Kindly Select your favourite sports</p>  \n\t\t<input type=\"checkbox\" name=\"sport1\" value=\"cricket\">Cricket<br>  \n\t\t<input type=\"checkbox\" name=\"sport2\" value=\"tennis\">Tennis<br>  \n\t\t<input type=\"checkbox\" name=\"sport3\" value=\"football\">Football<br>  \n\t\t<input type=\"color\" name=\"upclick\" value=\"#a52a2a\"> Upclick<br><br>  \n\t\t<input type=\"date\" name=\"Startdate\"> Start date:<br><br>  \n\t\t<label>Enter your age: </label>  \n\t\t<input type=\"number\" name=\"num\" min=\"50\" max=\"80\">  \n\t\t<label><b>Enter your Telephone Number(in format of xxx-xxx-xxxx):</b></label>  \n\t\t<input type=\"tel\" name=\"telephone\" pattern=\"[0-9]{3}-[0-9]{3}-[0-9]{4}\" required>  \n\t\t<p>Kindly Select your favourite food</p>  \n\t\t<select name=\"food\" id=\"food\">\n\t\t\t<option value=\"pizza\">Pizza</option>\n\t\t\t<option value=\"burger\">Burger</option>\n\t\t\t<option value=\"pasta\" selected>Pasta</option>\n\t\t</select>\n\t\t<p>Kindly Select your favourite country</p>  \n\t\t<select name=\"country\" id=\"country\">\n\t\t\t<option value=\"india\">India</option>\n\t\t\t<option value=\"usa\">USA</option>\n\t\t\t<option value=\"uk\">UK</option>\n\t\t\t<option value=\"canada\">Canada</option>\n\t\t</select>\n\t\t<label><b>Write some words about yourself:</b></label>\n\t\t<textarea id=\"message\" name=\"message\" rows=\"10\" cols=\"50\">\n\t\t\tWrite something here\n\t\t</textarea>\n\t\t\n\t\t\n\t\t<br><br><input type=\"submit\" value=\"submit\">\n\t\t\n\t</form>  \n</body>\n</html>`\n\nfunc TestFormInputFillSuggestions(t *testing.T) {\n\tdocument, err := goquery.NewDocumentFromReader(strings.NewReader(htmlFormInputExample))\n\trequire.NoError(t, err, \"could not read document\")\n\n\tdocument.Find(\"form[action]\").Each(func(i int, item *goquery.Selection) {\n\t\tqueryValuesWriter := make(url.Values)\n\t\tformFields := []interface{}{}\n\n\t\titem.Find(\"input, textarea, select\").Each(func(index int, item *goquery.Selection) {\n\t\t\tif len(item.Nodes) == 0 {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tformFields = append(formFields, ConvertGoquerySelectionToFormField(item))\n\t\t})\n\n\t\tdataMap := FormFillSuggestions(formFields)\n\t\tdataMap.Iterate(func(key, value string) bool {\n\t\t\tif key == \"\" || value == \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tqueryValuesWriter.Set(key, value)\n\t\t\treturn true\n\t\t})\n\t\tvalue := queryValuesWriter.Encode()\n\t\trequire.Equal(t, \"Startdate=katana&color=green&country=india&firstname=katana&food=pasta&message=katana&num=51&password=katana&sport1=cricket&sport2=tennis&sport3=football&telephone=katanaP%40assw0rd1&upclick=%23a52a2a\", value, \"could not get correct encoded form\")\n\t})\n}\n"
  },
  {
    "path": "pkg/utils/jsluice.go",
    "content": "//go:build !(386 || windows)\n\npackage utils\n\nimport (\n\t\"regexp\"\n\n\t\"github.com/BishopFox/jsluice\"\n)\n\nvar (\n\t// CommonJSLibraryFileRegex is a regex to match common js library files.\n\tCommonJSLibraryFileRegex = `(?i)(?:amplify|quantserve|slideshow|jquery|modernizr|polyfill|vendor|modules|gtm|underscore?|tween|retina|selectivizr|cufon|angular|swf|sha1|freestyle|bootstrap|d3|backbone|videojs|google[-_]analytics|material|redux|knockout|datepicker|datetimepicker|ember|react|ng|fusion|analytics|libs?|vendors?|node[-_]modules|lodash|moment|chart|highcharts|raphael|prototype|mootools|dojo|ext|yui|web[-_]?components|polymer|vue|svelte|next|nuxt|gatsby|express|koa|hapi|socket[-_.]?io|axios|superagent|request|bluebird|rxjs|ramda|immutable|flux|redux[-_]saga|mobx|relay|apollo|graphql|three|phaser|pixi|babylon|cannon|hammer|howler|gsap|velocity|mo[-_.]?js|popper|shepherd|prism|highlight|markdown[-_]?it|codemirror|ace[-_]?editor|tinymce|ckeditor|quill|simplemde|monaco[-_]?editor|pdf[-_.]?js|jspdf|fabric|paper|konva|p5|processing|matter[-_.]?js|box2d|planck|chart[-_.]?js|plotly|echarts|d3[-_.]?force|sigma|c3|nvd3|amcharts|vis[-_.]?js|dagre[-_.]?d3|cytoscape|leaflet|openlayers|ol3|mapbox|cesium|turf|moment[-_.]?timezone|luxon|dayjs|date[-_.]?fns|date[-_.]?io|flatpickr|pikaday|fullcalendar|draggable|interact|sortable|dragula|dropzone|filepond|uppy|fine[-_.]?uploader|plyr|mediaelement|flowplayer|jwplayer|video[-_.]?js|mediaelement[-_.]?js|dash[-_.]?js|hls[-_.]?js|videojs|wavesurfer|soundmanager|amplitude|pizzicato|tone|adroll|doubleclick|facebook-pixel|ga-audiences|googlesyndication|adsbygoogle|gpt|amazon-adsystem|criteo|taboola|outbrain|bidswitch|bidswitch.net|spotxchange|yahoo|media.net|contextweb|openx|pubmatic|rubiconproject|indexexchange|appnexus|liveintent|triplelift|verizonmedia|synacor|sonobi|yieldmo|gumgum|smartadserver|mopub|pubnative|inmobi|chartboost|tapjoy|admob|unityads|vungle|flurry|matomy|altitude|dataxu|thetradedesk|exponential|zypmedia|quantcast|mediamath|bidswitch|mgid|revcontent|powerlinks|rhythmone|airpush|smaato|adcolony|mopub|leadbolt|mobfox|nativo|revjet|smartyads|avocarrot|epom|imobile|supersonicads|loopme|applovin|pandora|mytarget|bidvertiser|chitika|popads|propellerads|buysellads|adhit|hilltopads|plugrush|popcash|popunder|revenuehits|trafficjunky|trafficfactory|zero-|smartoasis)(?:[-._][\\w\\d]*)*\\.js$`\n\tcommonJSLibraryFileRegexCompiled = regexp.MustCompile(CommonJSLibraryFileRegex)\n)\n\n// IsPathCommonJSLibraryFile checks if a given path is a common js library file.\nfunc IsPathCommonJSLibraryFile(path string) bool {\n\treturn commonJSLibraryFileRegexCompiled.MatchString(path)\n}\n\ntype JSLuiceEndpoint struct {\n\tEndpoint string\n\tType     string\n}\n\n// ExtractJsluiceEndpoints extracts jsluice endpoints from a given string.\n//\n// We use tomnomnom and bishopfox's jsluice to extract endpoints from javascript\n// files.\n//\n// We apply several optimizations before running jsluice:\n//   - We skip common js library files.\n//   - We skip lines that are too long and contain a lot of characters.\nfunc ExtractJsluiceEndpoints(data string) []JSLuiceEndpoint {\n\tanalyzer := jsluice.NewAnalyzer([]byte(data))\n\n\t// TODO: add new user url matchers\n\t// analyzer.AddURLMatcher(matcher)\n\n\tvar endpoints []JSLuiceEndpoint\n\tfoundURLs := analyzer.GetURLs()\n\n\tfor _, url := range foundURLs {\n\t\turl := url\n\t\tendpoints = append(endpoints, JSLuiceEndpoint{\n\t\t\tEndpoint: url.URL,\n\t\t\tType:     url.Type,\n\t\t})\n\t}\n\treturn endpoints\n}\n"
  },
  {
    "path": "pkg/utils/jsluice_test.go",
    "content": "//go:build !(386 || windows)\n\npackage utils\n\nimport \"testing\"\n\nfunc TestIsPathCommonJSLibraryFile(t *testing.T) {\n\ttype args struct {\n\t\tpath string\n\t}\n\ttests := []struct {\n\t\tname string\n\t\targs args\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"jquery.js\",\n\t\t\targs: args{\n\t\t\t\tpath: \"jquery.js\",\n\t\t\t},\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"app.js\",\n\t\t\targs: args{\n\t\t\t\tpath: \"app.js\",\n\t\t\t},\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := IsPathCommonJSLibraryFile(tt.args.path); got != tt.want {\n\t\t\t\tt.Errorf(\"IsPathCommonJSLibraryFile() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/maps.go",
    "content": "package utils\n\nimport mapsutil \"github.com/projectdiscovery/utils/maps\"\n\nfunc MergeDataMaps(dataMap1 *mapsutil.OrderedMap[string, string], dataMap2 mapsutil.OrderedMap[string, string]) {\n\tdataMap2.Iterate(func(key, value string) bool {\n\t\tdataMap1.Set(key, value)\n\t\treturn true\n\t})\n}\n"
  },
  {
    "path": "pkg/utils/maps_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\tmapsutil \"github.com/projectdiscovery/utils/maps\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestMergeDataMap(t *testing.T) {\n\t// Create two data maps\n\tdataMap1 := mapsutil.NewOrderedMap[string, string]()\n\tdataMap1.Set(\"key1\", \"value1\")\n\tdataMap1.Set(\"key2\", \"value2\")\n\n\tdataMap2 := mapsutil.NewOrderedMap[string, string]()\n\tdataMap2.Set(\"key3\", \"value3\")\n\tdataMap2.Set(\"key4\", \"value4\")\n\n\t// Merge the data maps\n\tMergeDataMaps(&dataMap1, dataMap2)\n\n\t// Verify the merged map contains all the keys and values\n\tvalue1, _ := dataMap1.Get(\"key1\")\n\tvalue2, _ := dataMap1.Get(\"key2\")\n\tvalue3, _ := dataMap1.Get(\"key3\")\n\tvalue4, _ := dataMap1.Get(\"key4\")\n\trequire.Equal(t, \"value1\", value1)\n\trequire.Equal(t, \"value2\", value2)\n\trequire.Equal(t, \"value3\", value3)\n\trequire.Equal(t, \"value4\", value4)\n}\n"
  },
  {
    "path": "pkg/utils/pathtrie.go",
    "content": "package utils\n\nimport (\n\t\"sync\"\n\n\tlru \"github.com/hashicorp/golang-lru/v2\"\n)\n\n// DefaultPromotionThreshold is the number of distinct children a trie node\n// must accumulate before being promoted to a parameter node.\nconst DefaultPromotionThreshold = 10\n\n// DefaultMaxHosts is the maximum number of hosts tracked by the LRU cache.\n// When exceeded, the least recently used host's trie is evicted.\nconst DefaultMaxHosts = 10000\n\n// PathTrie is a per-host adaptive trie that tracks unique path segments\n// at each position. When a node accumulates more distinct children than\n// the promotion threshold, it is promoted to a parameter node and all\n// future values at that position are collapsed.\n//\n// Host tracking is bounded by an LRU cache to prevent unbounded memory\n// growth during large crawls.\ntype PathTrie struct {\n\tmu        sync.Mutex\n\troots     *lru.Cache[string, *trieNode]\n\tthreshold int\n}\n\ntype trieNode struct {\n\tchildren   map[string]*trieNode\n\tparamChild *trieNode\n\tpromoted   bool\n}\n\n// NewPathTrie creates a new PathTrie with the given promotion threshold.\n// If threshold is <= 0, DefaultPromotionThreshold is used.\nfunc NewPathTrie(threshold int) *PathTrie {\n\tif threshold <= 0 {\n\t\tthreshold = DefaultPromotionThreshold\n\t}\n\tcache, _ := lru.New[string, *trieNode](DefaultMaxHosts)\n\treturn &PathTrie{\n\t\troots:     cache,\n\t\tthreshold: threshold,\n\t}\n}\n\n// Fingerprint walks the trie for the given host and segments, returning\n// a new slice where promoted positions are replaced with \"{param}\".\n// Non-promoted segments are registered in the trie for future cardinality tracking.\nfunc (t *PathTrie) Fingerprint(host string, segments []string) []string {\n\tt.mu.Lock()\n\tdefer t.mu.Unlock()\n\n\troot, ok := t.roots.Get(host)\n\tif !ok {\n\t\troot = &trieNode{children: make(map[string]*trieNode)}\n\t\tt.roots.Add(host, root)\n\t}\n\n\tresult := make([]string, len(segments))\n\tcurrent := root\n\n\tfor i, seg := range segments {\n\t\tif current.promoted {\n\t\t\tresult[i] = \"{param}\"\n\t\t\tif current.paramChild == nil {\n\t\t\t\tcurrent.paramChild = &trieNode{children: make(map[string]*trieNode)}\n\t\t\t}\n\t\t\tcurrent = current.paramChild\n\t\t\tcontinue\n\t\t}\n\n\t\tchild, exists := current.children[seg]\n\t\tif !exists {\n\t\t\tchild = &trieNode{children: make(map[string]*trieNode)}\n\t\t\tcurrent.children[seg] = child\n\n\t\t\tif len(current.children) > t.threshold {\n\t\t\t\tcurrent.promoted = true\n\t\t\t\tcurrent.paramChild = &trieNode{children: make(map[string]*trieNode)}\n\t\t\t\tcurrent.children = nil\n\t\t\t\tresult[i] = \"{param}\"\n\t\t\t\tcurrent = current.paramChild\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tresult[i] = seg\n\t\tcurrent = child\n\t}\n\n\treturn result\n}\n"
  },
  {
    "path": "pkg/utils/pathtrie_test.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestPathTrie_BasicInsertion(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\tsegments := []string{\"api\", \"v1\", \"users\"}\n\tresult := trie.Fingerprint(\"example.com\", segments)\n\n\tfor i, seg := range result {\n\t\tif seg != segments[i] {\n\t\t\tt.Errorf(\"segment %d = %q, want %q\", i, seg, segments[i])\n\t\t}\n\t}\n}\n\nfunc TestPathTrie_RepeatedInsertionNoop(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\tsegments := []string{\"api\", \"v1\", \"users\"}\n\n\t// Inserting the same path many times should not trigger promotion\n\tfor range 100 {\n\t\tresult := trie.Fingerprint(host, segments)\n\t\tfor i, seg := range result {\n\t\t\tif seg != segments[i] {\n\t\t\t\tt.Fatalf(\"repeated insertion changed segment %d: got %q, want %q\", i, seg, segments[i])\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestPathTrie_PromotionExactThreshold(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\t// Insert exactly threshold distinct children — should NOT promote\n\tfor i := range DefaultPromotionThreshold {\n\t\ttrie.Fingerprint(host, []string{\"items\", fmt.Sprintf(\"item-%d\", i)})\n\t}\n\tresult := trie.Fingerprint(host, []string{\"items\", \"item-0\"})\n\tif result[1] == \"{param}\" {\n\t\tt.Error(\"promoted at exactly threshold, should only promote when exceeding\")\n\t}\n\n\t// One more distinct child triggers promotion\n\tresult = trie.Fingerprint(host, []string{\"items\", \"item-trigger\"})\n\tif result[1] != \"{param}\" {\n\t\tt.Errorf(\"expected promotion after exceeding threshold, got %q\", result[1])\n\t}\n}\n\nfunc TestPathTrie_Promotion(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(host, []string{\"blog\", fmt.Sprintf(\"slug-%d\", i)})\n\t}\n\n\tresult := trie.Fingerprint(host, []string{\"blog\", \"brand-new-slug\"})\n\tif result[0] != \"blog\" {\n\t\tt.Errorf(\"static segment = %q, want %q\", result[0], \"blog\")\n\t}\n\tif result[1] != \"{param}\" {\n\t\tt.Errorf(\"promoted segment = %q, want %q\", result[1], \"{param}\")\n\t}\n}\n\nfunc TestPathTrie_PromotionDoesNotAffectOtherHosts(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(\"a.com\", []string{\"users\", fmt.Sprintf(\"user-%d\", i)})\n\t}\n\n\tresult := trie.Fingerprint(\"b.com\", []string{\"users\", \"alice\"})\n\tif result[1] != \"alice\" {\n\t\tt.Errorf(\"host isolation failed: got %q, want %q\", result[1], \"alice\")\n\t}\n}\n\nfunc TestPathTrie_DeepPath(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(host, []string{\"api\", \"users\", fmt.Sprintf(\"user-%d\", i), \"posts\"})\n\t}\n\n\tresult := trie.Fingerprint(host, []string{\"api\", \"users\", \"new-user\", \"posts\"})\n\texpected := []string{\"api\", \"users\", \"{param}\", \"posts\"}\n\tfor i, seg := range result {\n\t\tif seg != expected[i] {\n\t\t\tt.Errorf(\"segment %d = %q, want %q\", i, seg, expected[i])\n\t\t}\n\t}\n}\n\nfunc TestPathTrie_EmptySegments(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\tresult := trie.Fingerprint(\"example.com\", []string{})\n\tif len(result) != 0 {\n\t\tt.Errorf(\"expected empty result, got %v\", result)\n\t}\n}\n\nfunc TestPathTrie_SingleSegment(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(\"example.com\", []string{fmt.Sprintf(\"page-%d\", i)})\n\t}\n\n\tresult := trie.Fingerprint(\"example.com\", []string{\"page-new\"})\n\tif result[0] != \"{param}\" {\n\t\tt.Errorf(\"got %q, want {param}\", result[0])\n\t}\n}\n\nfunc TestPathTrie_PromotedNodeChildrenNil(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(host, []string{fmt.Sprintf(\"item-%d\", i)})\n\t}\n\n\troot, ok := trie.roots.Get(host)\n\tif !ok {\n\t\tt.Fatal(\"expected host root to exist\")\n\t}\n\tif root.children != nil {\n\t\tt.Error(\"expected children to be nil after promotion\")\n\t}\n\tif !root.promoted {\n\t\tt.Error(\"expected node to be promoted\")\n\t}\n}\n\nfunc TestPathTrie_PreviousValueCollapseAfterPromotion(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\t// Insert values before promotion\n\ttrie.Fingerprint(host, []string{\"blog\", \"first-post\"})\n\ttrie.Fingerprint(host, []string{\"blog\", \"second-post\"})\n\n\t// Trigger promotion\n\tfor i := 2; i <= DefaultPromotionThreshold; i++ {\n\t\ttrie.Fingerprint(host, []string{\"blog\", fmt.Sprintf(\"post-%d\", i)})\n\t}\n\n\t// Previously seen \"first-post\" should now collapse\n\tresult := trie.Fingerprint(host, []string{\"blog\", \"first-post\"})\n\tif result[1] != \"{param}\" {\n\t\tt.Errorf(\"previously seen value after promotion: got %q, want {param}\", result[1])\n\t}\n}\n\nfunc TestPathTrie_SegmentsAfterPromotedNodeTrackedIndependently(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\t// Promote the username segment: /users/{param}/...\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(host, []string{\"users\", fmt.Sprintf(\"user-%d\", i), \"profile\"})\n\t}\n\n\t// After username is promoted, segments AFTER it should still track independently\n\t// Feed \"profile\" and \"settings\" — two distinct values, should not promote\n\ttrie.Fingerprint(host, []string{\"users\", \"someone\", \"settings\"})\n\tresult := trie.Fingerprint(host, []string{\"users\", \"anyone\", \"settings\"})\n\n\tif result[1] != \"{param}\" {\n\t\tt.Errorf(\"promoted segment: got %q, want {param}\", result[1])\n\t}\n\t// \"settings\" should still be literal since paramChild only has 2 children\n\tif result[2] == \"{param}\" {\n\t\tt.Errorf(\"segment after promoted should not be promoted yet: got %q\", result[2])\n\t}\n}\n\nfunc TestPathTrie_MultipleBranchesIndependent(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\t// Promote /api/users/* but not /api/posts/*\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(host, []string{\"api\", \"users\", fmt.Sprintf(\"user-%d\", i)})\n\t}\n\t// Only add a few posts\n\ttrie.Fingerprint(host, []string{\"api\", \"posts\", \"first\"})\n\ttrie.Fingerprint(host, []string{\"api\", \"posts\", \"second\"})\n\n\t// users should be promoted\n\tresult := trie.Fingerprint(host, []string{\"api\", \"users\", \"new\"})\n\tif result[2] != \"{param}\" {\n\t\tt.Errorf(\"users branch should be promoted: got %q\", result[2])\n\t}\n\n\t// posts should NOT be promoted\n\tresult = trie.Fingerprint(host, []string{\"api\", \"posts\", \"third\"})\n\tif result[2] == \"{param}\" {\n\t\tt.Errorf(\"posts branch should not be promoted: got %q\", result[2])\n\t}\n}\n\nfunc TestPathTrie_MultiplePromotionsAtDifferentDepths(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"example.com\"\n\n\t// Promote at depth 0 (root children)\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\ttrie.Fingerprint(host, []string{fmt.Sprintf(\"section-%d\", i), \"page\"})\n\t}\n\n\tresult := trie.Fingerprint(host, []string{\"section-new\", \"page\"})\n\tif result[0] != \"{param}\" {\n\t\tt.Errorf(\"depth 0 promotion: got %q, want {param}\", result[0])\n\t}\n\n\t// Depth 1 should still track normally through paramChild\n\t// \"page\" is the only value at depth 1 through paramChild, so not promoted\n\tif result[1] == \"{param}\" {\n\t\tt.Errorf(\"depth 1 should not be promoted yet: got %q\", result[1])\n\t}\n}\n\nfunc TestPathTrie_NewHostLazyInit(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\n\t// Accessing new host should work without explicit init\n\tresult := trie.Fingerprint(\"new-host.com\", []string{\"foo\", \"bar\"})\n\tif result[0] != \"foo\" || result[1] != \"bar\" {\n\t\tt.Errorf(\"lazy init failed: got %v\", result)\n\t}\n\n\t// Verify the host was created in the LRU cache\n\tif !trie.roots.Contains(\"new-host.com\") {\n\t\tt.Error(\"host root not created after first access\")\n\t}\n}\n\nfunc TestPathTrie_CustomThreshold(t *testing.T) {\n\ttrie := NewPathTrie(3)\n\thost := \"example.com\"\n\n\t// With threshold=3, promotion happens after 4 distinct children\n\ttrie.Fingerprint(host, []string{\"items\", \"a\"})\n\ttrie.Fingerprint(host, []string{\"items\", \"b\"})\n\ttrie.Fingerprint(host, []string{\"items\", \"c\"})\n\n\t// 3 children, not promoted yet\n\tresult := trie.Fingerprint(host, []string{\"items\", \"a\"})\n\tif result[1] == \"{param}\" {\n\t\tt.Error(\"should not promote at threshold\")\n\t}\n\n\t// 4th distinct child triggers promotion\n\tresult = trie.Fingerprint(host, []string{\"items\", \"d\"})\n\tif result[1] != \"{param}\" {\n\t\tt.Errorf(\"expected promotion with threshold=3, got %q\", result[1])\n\t}\n}\n\nfunc TestPathTrie_LRUEviction(t *testing.T) {\n\t// Create a trie with a small LRU for testing eviction behavior\n\ttrie := NewPathTrie(0)\n\n\t// Add many hosts to verify the trie doesn't panic\n\tfor i := range 100 {\n\t\thost := fmt.Sprintf(\"host-%d.com\", i)\n\t\ttrie.Fingerprint(host, []string{\"path\", \"segment\"})\n\t}\n\n\t// All recently accessed hosts should still work\n\tresult := trie.Fingerprint(\"host-99.com\", []string{\"path\", \"segment\"})\n\tif result[0] != \"path\" || result[1] != \"segment\" {\n\t\tt.Errorf(\"LRU host access failed: got %v\", result)\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/queue/priority_queue.go",
    "content": "package queue\n\nimport (\n\t\"container/heap\"\n)\n\ntype priorityQueue struct {\n\titemHeap *itemHeap\n}\n\nfunc newPriorityQueue() *priorityQueue {\n\treturn &priorityQueue{itemHeap: &itemHeap{}}\n}\n\n// Len returns the number of elements in the queue.\nfunc (p *priorityQueue) Len() int {\n\treturn p.itemHeap.Len()\n}\n\nfunc (p *priorityQueue) Push(v interface{}, priority int) {\n\tnewItem := &item{\n\t\tvalue:    v,\n\t\tpriority: priority,\n\t}\n\theap.Push(p.itemHeap, newItem)\n}\n\nfunc (p *priorityQueue) Pop() interface{} {\n\tif len(*p.itemHeap) == 0 {\n\t\treturn nil\n\t}\n\titem := heap.Pop(p.itemHeap).(*item)\n\treturn item.value\n}\n\ntype itemHeap []*item\n\ntype item struct {\n\tvalue    interface{}\n\tpriority int\n\tindex    int\n}\n\nfunc (ih *itemHeap) Len() int {\n\treturn len(*ih)\n}\n\nfunc (ih *itemHeap) Less(i, j int) bool {\n\t// Less since we are executing lower prioritize values first and\n\t// higher priority values in the end.\n\treturn (*ih)[i].priority < (*ih)[j].priority\n}\n\nfunc (ih *itemHeap) Swap(i, j int) {\n\t(*ih)[i], (*ih)[j] = (*ih)[j], (*ih)[i]\n\t(*ih)[i].index = i\n\t(*ih)[j].index = j\n}\n\nfunc (ih *itemHeap) Push(x interface{}) {\n\tit := x.(*item)\n\tit.index = len(*ih)\n\t*ih = append(*ih, it)\n}\n\nfunc (ih *itemHeap) Pop() interface{} {\n\told := *ih\n\titem := old[len(old)-1]\n\t*ih = old[0 : len(old)-1]\n\treturn item\n}\n"
  },
  {
    "path": "pkg/utils/queue/priority_queue_test.go",
    "content": "package queue\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestPriorityQueue(t *testing.T) {\n\tqueue := newPriorityQueue()\n\tqueue.Push(\"lower\", 3)\n\tqueue.Push(\"higher\", 4)\n\tqueue.Push(\"lowest\", 1)\n\n\trequire.Equal(t, \"lowest\", queue.Pop(), \"could not pop lowest priority first\")\n\trequire.Equal(t, \"lower\", queue.Pop(), \"could not pop lower priority first\")\n\trequire.Equal(t, \"higher\", queue.Pop(), \"could not pop higher priority first\")\n}\n"
  },
  {
    "path": "pkg/utils/queue/queue.go",
    "content": "package queue\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n)\n\n// Queue is a queue that implements bucket based depth-first\n// or breadth-first queue.\n//\n// The breadth-first queues allow defining scores on whose\n// basis the bucket is distributed.  Lower scores are picked up first, and\n// higher scores which have a greater chance of being just random\n// noise are picked up later in depth first.\n//\n// Depth-first queue uses a simple stack for LIFO operations and distributes\n// items as they come in.\ntype Queue struct {\n\tsync.Mutex\n\tTimeout       time.Duration\n\tStrategy      Strategy\n\tstack         *stack\n\tpriorityQueue *priorityQueue\n}\n\n// New creates a new queue from the type specified.\nfunc New(strategyName string, timeout int) (*Queue, error) {\n\tstrategy, ok := strategiesMap[strategyName]\n\tif !ok {\n\t\treturn nil, errors.New(\"unsupported strategy\")\n\t}\n\n\tqueue := &Queue{\n\t\tStrategy:      strategy,\n\t\tTimeout:       time.Duration(timeout) * time.Second,\n\t\tstack:         newStack(),\n\t\tpriorityQueue: newPriorityQueue(),\n\t}\n\n\treturn queue, nil\n}\n\n// Len returns the number of items in queue.\nfunc (q *Queue) Len() int {\n\tq.Lock()\n\tdefer q.Unlock()\n\n\tswitch q.Strategy {\n\tcase BreadthFirst:\n\t\treturn q.priorityQueue.Len()\n\tcase DepthFirst:\n\t\treturn q.stack.Len()\n\t}\n\n\treturn 0\n}\n\n// Push pushes an element with an optional priority into the queue.\nfunc (q *Queue) Push(x interface{}, priority int) {\n\tq.Lock()\n\tdefer q.Unlock()\n\n\tswitch q.Strategy {\n\tcase BreadthFirst:\n\t\tq.priorityQueue.Push(x, priority)\n\tcase DepthFirst:\n\t\tq.stack.Push(x)\n\t}\n}\n\n// Pop pops an element from the queue. Result can be nil if no more\n// elements are present in the queue.\nfunc (q *Queue) Pop() chan interface{} {\n\titems := make(chan interface{})\n\n\tgo func() {\n\t\tstart := time.Now()\n\t\tfor {\n\t\t\tvar item interface{}\n\t\t\tq.Lock()\n\t\t\tswitch q.Strategy {\n\t\t\tcase BreadthFirst:\n\t\t\t\titem = q.priorityQueue.Pop()\n\t\t\tcase DepthFirst:\n\t\t\t\titem = q.stack.Pop()\n\t\t\t}\n\t\t\tq.Unlock()\n\n\t\t\tif item == nil {\n\t\t\t\tif !start.Add(q.Timeout).Before(time.Now()) {\n\t\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tclose(items)\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\titems <- item\n\t\t\t\tstart = time.Now()\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn items\n}\n"
  },
  {
    "path": "pkg/utils/queue/stack.go",
    "content": "package queue\n\nimport (\n\t\"container/list\"\n)\n\n// Taken from https://stackoverflow.com/a/64641330/9546749\ntype stack struct {\n\tll *list.List\n}\n\nfunc newStack() *stack {\n\treturn &stack{ll: list.New()}\n}\n\nfunc (s *stack) Push(x interface{}) {\n\ts.ll.PushBack(x)\n}\n\nfunc (s *stack) Len() int {\n\treturn s.ll.Len()\n}\n\nfunc (s *stack) Pop() interface{} {\n\tif s.ll.Len() == 0 {\n\t\treturn nil\n\t}\n\ttail := s.ll.Back()\n\tval := tail.Value\n\ts.ll.Remove(tail)\n\treturn val\n}\n"
  },
  {
    "path": "pkg/utils/queue/stack_test.go",
    "content": "package queue\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestStack(t *testing.T) {\n\tqueue := newStack()\n\tqueue.Push(\"lower\")\n\tqueue.Push(\"higher\")\n\tqueue.Push(\"lowest\")\n\n\trequire.Equal(t, \"lowest\", queue.Pop(), \"could not pop correct value\")\n\trequire.Equal(t, \"higher\", queue.Pop(), \"could not pop correct value\")\n\trequire.Equal(t, \"lower\", queue.Pop(), \"could not pop correct value\")\n}\n"
  },
  {
    "path": "pkg/utils/queue/strategy.go",
    "content": "package queue\n\n// Strategy of the queue\ntype Strategy int\n\n// strategies of queues available for selection.\nconst (\n\tBreadthFirst Strategy = iota\n\tDepthFirst\n)\n\nfunc (s Strategy) String() string {\n\tfor k, v := range strategiesMap {\n\t\tif v == s {\n\t\t\treturn k\n\t\t}\n\t}\n\treturn \"\"\n}\n\nvar strategiesMap = map[string]Strategy{\n\t\"breadth-first\": BreadthFirst,\n\t\"depth-first\":   DepthFirst,\n}\n"
  },
  {
    "path": "pkg/utils/regex.go",
    "content": "package utils\n\nimport (\n\t\"regexp\"\n)\n\nvar (\n\tBodyA0 = `(?:`\n\tBodyB0 = `(`\n\tBodyC0 = `(?:[\\.]{1,2}/[A-Za-z0-9\\-_/\\\\?&@\\.?=%]+)`\n\tBodyC1 = `|(https?://[A-Za-z0-9_\\-\\.]+([\\.]{0,2})?\\/[A-Za-z0-9\\-_/\\\\?&@\\.?=%]+)`\n\tBodyC2 = `|(/[A-Za-z0-9\\-_/\\\\?&@\\.%]+\\.(aspx?|action|cfm|cgi|do|pl|css|x?html?|js(p|on)?|pdf|php5?|py|rss))`\n\tBodyC3 = `|([A-Za-z0-9\\-_?&@\\.%]+/[A-Za-z0-9/\\\\\\-_?&@\\.%]+\\.(aspx?|action|cfm|cgi|do|pl|css|x?html?|js(p|on)?|pdf|php5?|py|rss))`\n\tBodyB1 = `)`\n\tBodyA1 = `)`\n\t// pageBodyRegex extracts endpoints from page body\n\tpageBodyRegex = regexp.MustCompile(BodyA0 + BodyB0 + BodyC0 + BodyC1 + BodyC2 + BodyC3 + BodyB1 + BodyA1)\n\n\tJsA0 = `(?:\"|'|\\s)`\n\tJsB0 = `(`\n\tJsC0 = `((https?://[A-Za-z0-9_\\-.]+(?:\\:\\d{1,5})?)+([\\.]{1,2})?/[A-Za-z0-9/\\-_\\\\.%]+(?:[\\?|#][^\"']+)?)`\n\tJsC1 = `|((\\.{1,2}/)?[a-zA-Z0-9\\-_/\\\\%]+\\.(aspx?|js(?:on|p)?|html|php5?|action|do)(?:[\\?|#][^\"']+)?)`\n\tJsC2 = `|((\\.{0,2}/)[a-zA-Z0-9\\-_/\\\\%]+(?:/|\\\\)[a-zA-Z0-9\\-_]{3,}(?:[\\?|#][^\"']+)?)`\n\tJsC3 = `|((\\.{0,2})[a-zA-Z0-9\\-_/\\\\%]{3,}/)`\n\tJsB1 = `)`\n\tJsA1 = `(?:\"|'|\\s)`\n\t// relativeEndpointsRegex is the regex to find endpoints in js files.\n\trelativeEndpointsRegex = regexp.MustCompile(JsA0 + JsB0 + JsC0 + JsC1 + JsC2 + JsC3 + JsB1 + JsA1)\n)\n\n// ExtractBodyEndpoints extracts body endpoints from a data item\nfunc ExtractBodyEndpoints(data string) []string {\n\tmatches := []string{}\n\tunique := make(map[string]struct{})\n\n\trelativeMatches := pageBodyRegex.FindAllStringSubmatch(data, -1)\n\tfor _, match := range relativeMatches {\n\t\tif len(match) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := unique[match[1]]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tunique[match[1]] = struct{}{}\n\t\tmatches = append(matches, match[1])\n\t}\n\treturn matches\n}\n\n// ExtractRelativeEndpoints extracts relative endpoints from a data item\nfunc ExtractRelativeEndpoints(data string) []string {\n\tmatches := []string{}\n\tunique := make(map[string]struct{})\n\n\trelativeMatches := relativeEndpointsRegex.FindAllStringSubmatch(data, -1)\n\tfor _, match := range relativeMatches {\n\t\tif len(match) < 2 {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := unique[match[1]]; ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tunique[match[1]] = struct{}{}\n\t\tmatches = append(matches, match[1])\n\t}\n\treturn matches\n}\n"
  },
  {
    "path": "pkg/utils/regex_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestPageBodyRegex(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname:  \"Mix of patterns\",\n\t\t\tinput: `Some text <a href=\"./rel/file.txt\">link1</a> and <img src=\"../rel2/file.php\"/> also http://a.com/b.html and https://c.com/d.aspx?p=1 finally /abs/path.js and rel/path/script.py end`,\n\t\t\texpected: []string{\n\t\t\t\t\"./rel/file.txt\",           // BodyC0\n\t\t\t\t\"../rel2/file.php\",         // BodyC0\n\t\t\t\t\"http://a.com/b.html\",      // BodyC1\n\t\t\t\t\"https://c.com/d.aspx?p=1\", // BodyC1\n\t\t\t\t\"/abs/path.js\",             // BodyC2\n\t\t\t\t\"rel/path/script.py\",       // BodyC3\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"No matches\",\n\t\t\tinput:    \"Just some plain text without any URLs or paths.\",\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only BodyC0\",\n\t\t\tinput:    `\"./path1\" '../path2'`,\n\t\t\texpected: []string{\"./path1\", \"../path2\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only BodyC1\",\n\t\t\tinput:    `http://example.com/page1 https://secure.com/page2`,\n\t\t\texpected: []string{\"http://example.com/page1\", \"https://secure.com/page2\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only BodyC2\",\n\t\t\tinput:    `\"/path/to/file.css\" '/another/script.js'`,\n\t\t\texpected: []string{\"/path/to/file.css\", \"/another/script.js\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only BodyC3\",\n\t\t\tinput:    `\"relative/path/file.php\" 'another/relative/page.html'`,\n\t\t\texpected: []string{\"relative/path/file.php\", \"another/relative/page.html\"},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmatches := pageBodyRegex.FindAllStringSubmatch(tc.input, -1)\n\t\t\tactual := []string{}\n\t\t\tfor _, match := range matches {\n\t\t\t\tif len(match) > 1 {\n\t\t\t\t\tactual = append(actual, match[1]) // Extract the first capture group\n\t\t\t\t}\n\t\t\t}\n\t\t\tassert.ElementsMatch(t, tc.expected, actual, \"Input: %s\", tc.input)\n\t\t})\n\t}\n}\n\nfunc TestRelativeEndpointsRegex(t *testing.T) {\n\ttestCases := []struct {\n\t\tname     string\n\t\tinput    string\n\t\texpected []string\n\t}{\n\t\t{\n\t\t\tname:  \"Mix of patterns in JS-like context\",\n\t\t\tinput: `var u1 = \"https://d.com/e.php?q=1\"; let u2 = './f/g.js'; const u3 = '../h/i.html'; func('/j/k/lll'); load('m/nnn/'); action(\"o/p.action\");`,\n\t\t\texpected: []string{\n\t\t\t\t\"https://d.com/e.php?q=1\", // JsC0\n\t\t\t\t\"./f/g.js\",                // JsC1\n\t\t\t\t\"../h/i.html\",             // JsC1\n\t\t\t\t\"/j/k/lll\",                // JsC2\n\t\t\t\t\"m/nnn/\",                  // JsC3\n\t\t\t\t\"o/p.action\",              // JsC1\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"No matches\",\n\t\t\tinput:    \"var x = 1; let y = 'hello'; const z = true;\",\n\t\t\texpected: []string{},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only JsC0\",\n\t\t\tinput:    `\"https://example.com/api/v1?key=123\" 'http://localhost:8080/test#section'`,\n\t\t\texpected: []string{\"https://example.com/api/v1?key=123\", \"http://localhost:8080/test#section\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only JsC1\",\n\t\t\tinput:    `\"./script.js\" 'page.php?id=5'`,\n\t\t\texpected: []string{\"./script.js\", \"page.php?id=5\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only JsC2\",\n\t\t\tinput:    `\"/api/v2/users\" '/data/items/fetch'`,\n\t\t\texpected: []string{\"/api/v2/users\", \"/data/items/fetch\"},\n\t\t},\n\t\t{\n\t\t\tname:     \"Only JsC3\",\n\t\t\tinput:    `\"./images/\" '../assets/' \"static/\"`,\n\t\t\texpected: []string{\"./images/\", \"../assets/\", \"static/\"},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmatches := relativeEndpointsRegex.FindAllStringSubmatch(tc.input, -1)\n\t\t\tactual := []string{}\n\t\t\tfor _, match := range matches {\n\t\t\t\tif len(match) > 1 {\n\t\t\t\t\tactual = append(actual, match[1]) // Extract the first capture group\n\t\t\t\t}\n\t\t\t}\n\t\t\tassert.ElementsMatch(t, tc.expected, actual, \"Input: %s\", tc.input)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/scope/scope.go",
    "content": "package scope\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"golang.org/x/net/publicsuffix\"\n)\n\n// Manager manages scope for crawling process\ntype Manager struct {\n\tinScope           []*regexp.Regexp\n\toutOfScope        []*regexp.Regexp\n\tnoScope           bool\n\tfieldScope        dnsScopeField\n\tfieldScopePattern *regexp.Regexp\n}\n\ntype dnsScopeField int\n\nconst (\n\tdnDnsScopeField dnsScopeField = iota + 1\n\trdnDnsScopeField\n\tfqdnDNSScopeField\n\tcustomDNSScopeField\n)\n\nvar stringToDNSScopeField = map[string]dnsScopeField{\n\t\"dn\":   dnDnsScopeField,\n\t\"rdn\":  rdnDnsScopeField,\n\t\"fqdn\": fqdnDNSScopeField,\n}\n\n// NewManager returns a new scope manager for crawling\nfunc NewManager(inScope, outOfScope []string, fieldScope string, noScope bool) (*Manager, error) {\n\tmanager := &Manager{\n\t\tnoScope: noScope,\n\t}\n\n\tif scopeValue, ok := stringToDNSScopeField[fieldScope]; !ok {\n\t\tmanager.fieldScope = customDNSScopeField\n\t\tif compiled, err := regexp.Compile(fieldScope); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not compile regex %s: %s\", fieldScope, err)\n\t\t} else {\n\t\t\tmanager.fieldScopePattern = compiled\n\t\t}\n\t} else {\n\t\tmanager.fieldScope = scopeValue\n\t}\n\tfor _, regex := range inScope {\n\t\tif compiled, err := regexp.Compile(regex); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not compile regex %s: %s\", regex, err)\n\t\t} else {\n\t\t\tmanager.inScope = append(manager.inScope, compiled)\n\t\t}\n\t}\n\tfor _, regex := range outOfScope {\n\t\tif compiled, err := regexp.Compile(regex); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not compile regex %s: %s\", regex, err)\n\t\t} else {\n\t\t\tmanager.outOfScope = append(manager.outOfScope, compiled)\n\t\t}\n\t}\n\treturn manager, nil\n}\n\n// Validate returns true if the URL matches scope rules.\n// When noScope is true, DNS validation is skipped but URL-based scope rules still apply.\nfunc (m *Manager) Validate(URL *url.URL, rootHostname string) (bool, error) {\n\tif !m.noScope {\n\t\t// Only validate DNS if scope is enabled\n\t\thostname := URL.Hostname()\n\t\tdnsValidated, err := m.validateDNS(hostname, rootHostname)\n\t\tif err != nil || !dnsValidated {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\tif len(m.inScope) > 0 || len(m.outOfScope) > 0 {\n\t\turlValidated, err := m.validateURL(URL.String())\n\t\tif err != nil || !urlValidated {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// validateURL checks whether the given URL matches the configured inScope and outOfScope patterns.\n// It returns true if the URL is allowed (matches inScope and doesn't match outOfScope),\n// false if rejected, and an error if pattern matching fails.\n// When both inScope and outOfScope are empty, it returns true with no error.\nfunc (m *Manager) validateURL(URL string) (bool, error) {\n\tfor _, item := range m.outOfScope {\n\t\tif item.MatchString(URL) {\n\t\t\treturn false, nil\n\t\t}\n\t}\n\tif len(m.inScope) == 0 {\n\t\treturn true, nil\n\t}\n\n\tvar inScopeMatched bool\n\tfor _, item := range m.inScope {\n\t\tif item.MatchString(URL) {\n\t\t\tinScopeMatched = true\n\t\t\tbreak\n\t\t}\n\t}\n\treturn inScopeMatched, nil\n}\n\n// validateDNS performs DNS-based scope validation by checking if the URL's hostname\n// matches the configured host-based scope rules. It returns true if the hostname\n// is within scope, false if out of scope, and an error if DNS resolution or\n// validation fails.\nfunc (m *Manager) validateDNS(hostname, rootHostname string) (bool, error) {\n\tparsed := net.ParseIP(hostname)\n\tif m.fieldScope == customDNSScopeField {\n\t\t// If we have a custom regex, we need to match it against the full hostname\n\t\tif m.fieldScopePattern.MatchString(hostname) {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\tif m.fieldScope == fqdnDNSScopeField || parsed != nil {\n\t\tmatched := strings.EqualFold(hostname, rootHostname)\n\t\treturn matched, nil\n\t}\n\n\trdn, dn, err := getDomainRDNandRDN(rootHostname)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tswitch m.fieldScope {\n\tcase dnDnsScopeField:\n\t\treturn strings.Contains(hostname, dn), nil\n\tcase rdnDnsScopeField:\n\t\treturn strings.HasSuffix(hostname, rdn), nil\n\t}\n\treturn false, nil\n}\n\n// getDomainRDNandRDN extracts and returns the root domain name (RDN) and the\n// effective top-level domain plus one label (eTLD+1) from the given hostname.\n// It returns empty strings and an error if the hostname cannot be parsed.\nfunc getDomainRDNandRDN(domain string) (string, string, error) {\n\tif strings.HasPrefix(domain, \".\") || strings.HasSuffix(domain, \".\") || strings.Contains(domain, \"..\") {\n\t\treturn \"\", \"\", fmt.Errorf(\"publicsuffix: empty label in domain %q\", domain)\n\t}\n\tsuffix, _ := publicsuffix.PublicSuffix(domain)\n\tif len(domain) <= len(suffix) {\n\t\treturn domain, \"\", nil\n\t}\n\ti := len(domain) - len(suffix) - 1\n\tif domain[i] != '.' {\n\t\treturn domain, \"\", nil\n\t}\n\treturn domain[1+strings.LastIndex(domain[:i], \".\"):], domain[1+strings.LastIndex(domain[:i], \".\") : len(domain)-len(suffix)-1], nil\n}\n"
  },
  {
    "path": "pkg/utils/scope/scope_test.go",
    "content": "package scope\n\nimport (\n\t\"testing\"\n\n\turlutil \"github.com/projectdiscovery/utils/url\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestManagerValidate verifies the Manager's Validate method with various scope configurations,\n// including URL pattern matching and host-based DNS validation for different scope types (dn, rdn, fqdn).\nfunc TestManagerValidate(t *testing.T) {\n\tt.Run(\"url\", func(t *testing.T) {\n\t\tmanager, err := NewManager([]string{`example`}, []string{`logout\\.php`}, \"dn\", false)\n\t\trequire.NoError(t, err, \"could not create scope manager\")\n\n\t\tparsed, _ := urlutil.Parse(\"https://test.com/index.php/example\")\n\t\tvalidated, err := manager.Validate(parsed.URL, \"test.com\")\n\t\trequire.NoError(t, err, \"could not validate url\")\n\t\trequire.True(t, validated, \"could not get correct in-scope validation\")\n\n\t\tparsed, _ = urlutil.Parse(\"https://test.com/logout.php\")\n\t\tvalidated, err = manager.Validate(parsed.URL, \"another.com\")\n\t\trequire.NoError(t, err, \"could not validate url\")\n\t\trequire.False(t, validated, \"could not get correct out-scope validation\")\n\t})\n\tt.Run(\"host\", func(t *testing.T) {\n\t\tt.Run(\"dn\", func(t *testing.T) {\n\t\t\tmanager, err := NewManager(nil, nil, \"dn\", false)\n\t\t\trequire.NoError(t, err, \"could not create scope manager\")\n\n\t\t\tparsed, _ := urlutil.Parse(\"https://testanother.com/index.php\")\n\t\t\tvalidated, err := manager.Validate(parsed.URL, \"test.com\")\n\t\t\trequire.NoError(t, err, \"could not validate host\")\n\t\t\trequire.True(t, validated, \"could not get correct in-scope validation\")\n\t\t})\n\t\tt.Run(\"rdn\", func(t *testing.T) {\n\t\t\tmanager, err := NewManager(nil, nil, \"rdn\", false)\n\t\t\trequire.NoError(t, err, \"could not create scope manager\")\n\n\t\t\tparsed, _ := urlutil.Parse(\"https://subdomain.example.com/logout.php\")\n\t\t\tvalidated, err := manager.Validate(parsed.URL, \"example.com\")\n\t\t\trequire.NoError(t, err, \"could not validate host\")\n\t\t\trequire.True(t, validated, \"could not get correct in-scope validation\")\n\t\t})\n\t\tt.Run(\"localhost\", func(t *testing.T) {\n\t\t\tmanager, err := NewManager(nil, nil, \"rdn\", false)\n\t\t\trequire.NoError(t, err, \"could not create scope manager\")\n\n\t\t\tparsed, _ := urlutil.Parse(\"http://localhost:8082/logout.php\")\n\t\t\tvalidated, err := manager.Validate(parsed.URL, \"localhost\")\n\t\t\trequire.NoError(t, err, \"could not validate host\")\n\t\t\trequire.True(t, validated, \"could not get correct in-scope validation\")\n\t\t})\n\t\tt.Run(\"fqdn\", func(t *testing.T) {\n\t\t\tmanager, err := NewManager(nil, nil, \"fqdn\", false)\n\t\t\trequire.NoError(t, err, \"could not create scope manager\")\n\n\t\t\tparsed, _ := urlutil.Parse(\"https://test.com/index.php\")\n\t\t\tvalidated, err := manager.Validate(parsed.URL, \"test.com\")\n\t\t\trequire.NoError(t, err, \"could not validate host\")\n\t\t\trequire.True(t, validated, \"could not get correct in-scope validation\")\n\n\t\t\tparsed, _ = urlutil.Parse(\"https://subdomain.example.com/logout.php\")\n\t\t\tvalidated, err = manager.Validate(parsed.URL, \"example.com\")\n\t\t\trequire.NoError(t, err, \"could not validate host\")\n\t\t\trequire.False(t, validated, \"could not get correct out-scope validation\")\n\n\t\t\tparsed, _ = urlutil.Parse(\"https://example.com/logout.php\")\n\t\t\tvalidated, err = manager.Validate(parsed.URL, \"another.com\")\n\t\t\trequire.NoError(t, err, \"could not validate host\")\n\t\t\trequire.False(t, validated, \"could not get correct out-scope validation\")\n\t\t})\n\t})\n}\n\n// TestGetDomainRDNandDN verifies the extraction of root domain name (RDN) and\n// effective top-level domain plus one label (eTLD+1) from a hostname.\nfunc TestGetDomainRDNandDN(t *testing.T) {\n\trdn, dn, err := getDomainRDNandRDN(\"test.projectdiscovery.io\")\n\trequire.Nil(t, err, \"could not get domain rdn and dn\")\n\trequire.Equal(t, \"projectdiscovery.io\", rdn, \"could not get correct rdn\")\n\trequire.Equal(t, \"projectdiscovery\", dn, \"could not get correct dn\")\n}\n\n// TestNoScopeWithOutOfScope verifies that when noScope is enabled, host-based\n// DNS validation is bypassed while URL pattern matching (inScope/outOfScope rules)\n// continues to function correctly. It tests scenarios with only outOfScope patterns\n// and with both inScope and outOfScope patterns to ensure proper filtering behavior.\nfunc TestNoScopeWithOutOfScope(t *testing.T) {\n\tt.Run(\"noScope with outOfScope rules\", func(t *testing.T) {\n\t\t// Create manager with noScope=true and outOfScope patterns\n\t\toutOfScopePatterns := []string{\n\t\t\t`logout\\.php`,\n\t\t\t`/admin/`,\n\t\t\t`\\.js$`,\n\t\t\t`^https?://[^/]+/\\?lang=[a-z]{2}`,\n\t\t}\n\t\tmanager, err := NewManager(nil, outOfScopePatterns, \"rdn\", true)\n\t\trequire.NoError(t, err, \"could not create scope manager with noScope and outOfScope\")\n\n\t\t// Test 1: URL from different domain should be allowed (noScope ignores DNS)\n\t\tparsed, _ := urlutil.Parse(\"https://completely-different.com/index.php\")\n\t\tvalidated, err := manager.Validate(parsed.URL, \"original.com\")\n\t\trequire.NoError(t, err, \"could not validate cross-domain URL with noScope\")\n\t\trequire.True(t, validated, \"cross-domain URL should be allowed with noScope\")\n\n\t\t// Test 2: URL matching outOfScope pattern should be rejected\n\t\tparsed, _ = urlutil.Parse(\"https://completely-different.com/logout.php\")\n\t\tvalidated, err = manager.Validate(parsed.URL, \"original.com\")\n\t\trequire.NoError(t, err, \"could not validate outOfScope URL\")\n\t\trequire.False(t, validated, \"outOfScope pattern should still be applied with noScope\")\n\n\t\t// Test 3: Normal URLs should be allowed\n\t\tparsed, _ = urlutil.Parse(\"https://any-site.com/products/item123\")\n\t\tvalidated, err = manager.Validate(parsed.URL, \"original.com\")\n\t\trequire.NoError(t, err, \"could not validate normal URL\")\n\t\trequire.True(t, validated, \"normal URLs should be allowed\")\n\t})\n\n\tt.Run(\"noScope with both inScope and outOfScope\", func(t *testing.T) {\n\t\t// Test combining noScope with both inScope and outOfScope\n\t\tinScopePatterns := []string{`/api/`, `/products/`}\n\t\toutOfScopePatterns := []string{`/api/internal/`, `\\.css$`}\n\n\t\tmanager, err := NewManager(inScopePatterns, outOfScopePatterns, \"fqdn\", true)\n\t\trequire.NoError(t, err, \"could not create manager with both scope types\")\n\n\t\t// Should be allowed: matches inScope, doesn't match outOfScope\n\t\tparsed, _ := urlutil.Parse(\"https://external.com/api/users\")\n\t\tvalidated, err := manager.Validate(parsed.URL, \"original.com\")\n\t\trequire.NoError(t, err, \"could not validate API endpoint\")\n\t\trequire.True(t, validated, \"API endpoint should be allowed\")\n\n\t\t// Should be rejected: matches both inScope and outOfScope (outOfScope wins)\n\t\tparsed, _ = urlutil.Parse(\"https://external.com/api/internal/secrets\")\n\t\tvalidated, err = manager.Validate(parsed.URL, \"original.com\")\n\t\trequire.NoError(t, err, \"could not validate internal API\")\n\t\trequire.False(t, validated, \"internal API should be excluded by outOfScope\")\n\n\t\t// Should be rejected: doesn't match inScope\n\t\tparsed, _ = urlutil.Parse(\"https://external.com/about/company\")\n\t\tvalidated, err = manager.Validate(parsed.URL, \"original.com\")\n\t\trequire.NoError(t, err, \"could not validate about page\")\n\t\trequire.False(t, validated, \"about page should be rejected (not in inScope)\")\n\n\t\t// Should be rejected: matches outOfScope\n\t\tparsed, _ = urlutil.Parse(\"https://external.com/styles/main.css\")\n\t\tvalidated, err = manager.Validate(parsed.URL, \"original.com\")\n\t\trequire.NoError(t, err, \"could not validate CSS file\")\n\t\trequire.False(t, validated, \"CSS files should be excluded\")\n\t})\n}\n"
  },
  {
    "path": "pkg/utils/urlfingerprint.go",
    "content": "package utils\n\nimport (\n\t\"net/url\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// segmentPattern defines a regex pattern and its replacement placeholder\n// for identifying variable path segments.\ntype segmentPattern struct {\n\tregex       *regexp.Regexp\n\tplaceholder string\n\t// validate provides an additional check beyond the regex match.\n\t// When nil, the regex match alone is sufficient.\n\tvalidate func(string) bool\n}\n\n// containsHexLetter returns true if the string has at least one a-f/A-F character.\n// Pure-numeric strings that happen to be ≥8 digits should fall through to\n// timestamp/numeric patterns instead of matching as hex.\nfunc containsHexLetter(s string) bool {\n\tfor _, c := range s {\n\t\tif (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// segmentPatterns are tested in order from most specific to most general.\n// Each pattern is anchored (^...$) to match complete path segments only.\nvar segmentPatterns = []segmentPattern{\n\t{regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`), \"{uuid}\", nil},\n\t{regexp.MustCompile(`^[0-9a-fA-F]{64}$`), \"{sha256}\", containsHexLetter},\n\t{regexp.MustCompile(`^[0-9a-fA-F]{40}$`), \"{sha1}\", containsHexLetter},\n\t{regexp.MustCompile(`^[0-9a-fA-F]{32}$`), \"{md5}\", containsHexLetter},\n\t{regexp.MustCompile(`^[0-9a-fA-F]{24}$`), \"{oid}\", containsHexLetter},\n\t{regexp.MustCompile(`^[0-9a-fA-F]{8,}$`), \"{hex}\", containsHexLetter},\n\t{regexp.MustCompile(`^\\d{4}-\\d{2}-\\d{2}$`), \"{date}\", nil},\n\t{regexp.MustCompile(`^\\d{10}(\\d{3})?$`), \"{ts}\", nil},\n\t{regexp.MustCompile(`^\\d+$`), \"{num}\", nil},\n}\n\n// normalizeSegment checks a single path segment against heuristic patterns.\n// Returns the placeholder if matched, or the original segment if no pattern matches.\nfunc normalizeSegment(segment string) (string, bool) {\n\tfor _, p := range segmentPatterns {\n\t\tif p.regex.MatchString(segment) {\n\t\t\tif p.validate != nil && !p.validate(segment) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn p.placeholder, true\n\t\t}\n\t}\n\treturn segment, false\n}\n\n// FingerprintURL produces a structural fingerprint of the given URL by:\n// 1. Replacing variable path segments (IDs, UUIDs, hashes, dates) with placeholders\n// 2. Using the adaptive trie (if provided) to detect learned parameter positions\n// 3. Dropping query parameter values, keeping only sorted keys\n//\n// When trie is nil, only Layer 1 regex-based normalization is applied.\nfunc FingerprintURL(rawURL string, trie *PathTrie) string {\n\tu, err := url.Parse(rawURL)\n\tif err != nil {\n\t\treturn rawURL\n\t}\n\n\tpath := u.Path\n\tif path == \"\" || path == \"/\" {\n\t\treturn buildFingerprint(u, path)\n\t}\n\n\t// Split path into segments, preserving leading slash\n\ttrimmed := strings.Trim(path, \"/\")\n\tif trimmed == \"\" {\n\t\treturn buildFingerprint(u, \"/\")\n\t}\n\tsegments := strings.Split(trimmed, \"/\")\n\n\t// Layer 1: heuristic regex normalization\n\tfor i, seg := range segments {\n\t\tif placeholder, matched := normalizeSegment(seg); matched {\n\t\t\tsegments[i] = placeholder\n\t\t}\n\t}\n\n\t// Layer 2: adaptive trie normalization\n\tif trie != nil {\n\t\tsegments = trie.Fingerprint(u.Hostname(), segments)\n\t}\n\n\tfingerprintedPath := \"/\" + strings.Join(segments, \"/\")\n\tif strings.HasSuffix(path, \"/\") {\n\t\tfingerprintedPath += \"/\"\n\t}\n\n\treturn buildFingerprint(u, fingerprintedPath)\n}\n\n// buildFingerprint reconstructs the URL with the fingerprinted path\n// and sorted query keys (values dropped).\nfunc buildFingerprint(u *url.URL, path string) string {\n\tvar b strings.Builder\n\tif u.Scheme != \"\" {\n\t\tb.WriteString(u.Scheme)\n\t\tb.WriteString(\"://\")\n\t}\n\tb.WriteString(u.Host)\n\tb.WriteString(path)\n\n\tif u.RawQuery != \"\" {\n\t\tkeys := sortedQueryKeys(u.Query())\n\t\tif len(keys) > 0 {\n\t\t\tb.WriteByte('?')\n\t\t\tb.WriteString(strings.Join(keys, \"&\"))\n\t\t}\n\t}\n\n\treturn b.String()\n}\n\n// sortedQueryKeys extracts and sorts query parameter keys.\nfunc sortedQueryKeys(params url.Values) []string {\n\tkeys := make([]string, 0, len(params))\n\tfor k := range params {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\treturn keys\n}\n"
  },
  {
    "path": "pkg/utils/urlfingerprint_test.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestContainsHexLetter(t *testing.T) {\n\ttests := []struct {\n\t\tinput string\n\t\twant  bool\n\t}{\n\t\t{\"abcdef01\", true},\n\t\t{\"ABCDEF01\", true},\n\t\t{\"1234abcd\", true},\n\t\t{\"12345678\", false},\n\t\t{\"00000000\", false},\n\t\t{\"0000000a\", true},\n\t\t{\"\", false},\n\t\t{\"g\", false}, // not a hex letter\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.input, func(t *testing.T) {\n\t\t\tif got := containsHexLetter(tt.input); got != tt.want {\n\t\t\t\tt.Errorf(\"containsHexLetter(%q) = %v, want %v\", tt.input, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNormalizeSegment(t *testing.T) {\n\ttests := []struct {\n\t\tsegment string\n\t\twant    string\n\t\tmatched bool\n\t}{\n\t\t// UUID\n\t\t{\"550e8400-e29b-41d4-a716-446655440000\", \"{uuid}\", true},\n\t\t{\"550E8400-E29B-41D4-A716-446655440000\", \"{uuid}\", true},\n\t\t// UUID with all-numeric groups still matches (dashes are the anchor)\n\t\t{\"12345678-1234-1234-1234-123456789012\", \"{uuid}\", true},\n\n\t\t// SHA256\n\t\t{\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\", \"{sha256}\", true},\n\t\t// 64-digit pure numeric should NOT match sha256 — falls to {num}\n\t\t{\"1234567890123456789012345678901234567890123456789012345678901234\", \"{num}\", true},\n\n\t\t// SHA1\n\t\t{\"da39a3ee5e6b4b0d3255bfef95601890afd80709\", \"{sha1}\", true},\n\t\t// 40-digit pure numeric should NOT match sha1 — falls to {ts} or {num}\n\t\t{\"1234567890123456789012345678901234567890\", \"{num}\", true},\n\n\t\t// MD5\n\t\t{\"d41d8cd98f00b204e9800998ecf8427e\", \"{md5}\", true},\n\t\t// 32-digit pure numeric should NOT match md5 — falls to {num}\n\t\t{\"12345678901234567890123456789012\", \"{num}\", true},\n\n\t\t// ObjectId (MongoDB 24 hex chars)\n\t\t{\"507f1f77bcf86cd799439011\", \"{oid}\", true},\n\t\t// 24-digit pure numeric — falls to {num}\n\t\t{\"123456789012345678901234\", \"{num}\", true},\n\n\t\t// Long hex (>= 8 chars, requires hex letter)\n\t\t{\"abcdef01\", \"{hex}\", true},\n\t\t{\"1234abcd5678\", \"{hex}\", true},\n\t\t{\"DEADBEEF\", \"{hex}\", true},\n\t\t// 8-digit pure numeric — NOT hex, falls to {num}\n\t\t{\"12345678\", \"{num}\", true},\n\t\t// 9-digit pure numeric — NOT hex, falls to {num}\n\t\t{\"123456789\", \"{num}\", true},\n\n\t\t// ISO date\n\t\t{\"2024-01-15\", \"{date}\", true},\n\t\t{\"2023-12-31\", \"{date}\", true},\n\t\t{\"1999-01-01\", \"{date}\", true},\n\t\t// Not a date (wrong format) — pure digits, no hex letters, falls to {num}\n\t\t{\"20240115\", \"{num}\", true},\n\n\t\t// Timestamp (10 digits)\n\t\t{\"1704067200\", \"{ts}\", true},\n\t\t// Timestamp (13 digits)\n\t\t{\"1704067200000\", \"{ts}\", true},\n\t\t// 11 digits — not a timestamp, falls to {num}\n\t\t{\"17040672001\", \"{num}\", true},\n\t\t// 14 digits — not a timestamp, falls to {num}\n\t\t{\"17040672000001\", \"{num}\", true},\n\n\t\t// Numeric\n\t\t{\"123\", \"{num}\", true},\n\t\t{\"0\", \"{num}\", true},\n\t\t{\"999999\", \"{num}\", true},\n\t\t{\"1\", \"{num}\", true},\n\n\t\t// Non-matching\n\t\t{\"users\", \"users\", false},\n\t\t{\"api\", \"api\", false},\n\t\t{\"v1\", \"v1\", false},\n\t\t{\"v2\", \"v2\", false},\n\t\t{\"my-awesome-post\", \"my-awesome-post\", false},\n\t\t{\"image123.jpg\", \"image123.jpg\", false},\n\t\t{\"style.css\", \"style.css\", false},\n\t\t{\"index.html\", \"index.html\", false},\n\t\t{\"abcdef\", \"abcdef\", false},  // 6-char hex, below 8-char threshold\n\t\t{\"feedback\", \"feedback\", false}, // valid hex chars but also contains non-hex\n\t\t{\"deadbeef-cafe\", \"deadbeef-cafe\", false}, // dash in wrong position, not UUID format\n\t\t{\"\", \"\", false},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.segment, func(t *testing.T) {\n\t\t\tgot, matched := normalizeSegment(tt.segment)\n\t\t\tif got != tt.want || matched != tt.matched {\n\t\t\t\tt.Errorf(\"normalizeSegment(%q) = (%q, %v), want (%q, %v)\",\n\t\t\t\t\ttt.segment, got, matched, tt.want, tt.matched)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFingerprintURL_RegexOnly(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\turl  string\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"numeric path segments\",\n\t\t\turl:  \"https://example.com/api/v1/users/123/posts/456\",\n\t\t\twant: \"https://example.com/api/v1/users/{num}/posts/{num}\",\n\t\t},\n\t\t{\n\t\t\tname: \"uuid in path\",\n\t\t\turl:  \"https://example.com/product/550e8400-e29b-41d4-a716-446655440000\",\n\t\t\twant: \"https://example.com/product/{uuid}\",\n\t\t},\n\t\t{\n\t\t\tname: \"date in path\",\n\t\t\turl:  \"https://example.com/archive/2024-01-15/article\",\n\t\t\twant: \"https://example.com/archive/{date}/article\",\n\t\t},\n\t\t{\n\t\t\tname: \"query params sorted and values dropped\",\n\t\t\turl:  \"https://example.com/search?z=1&a=2&m=3\",\n\t\t\twant: \"https://example.com/search?a&m&z\",\n\t\t},\n\t\t{\n\t\t\tname: \"no variable segments unchanged\",\n\t\t\turl:  \"https://example.com/about/team\",\n\t\t\twant: \"https://example.com/about/team\",\n\t\t},\n\t\t{\n\t\t\tname: \"root path\",\n\t\t\turl:  \"https://example.com/\",\n\t\t\twant: \"https://example.com/\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty path\",\n\t\t\turl:  \"https://example.com\",\n\t\t\twant: \"https://example.com\",\n\t\t},\n\t\t{\n\t\t\tname: \"mixed pattern types in one path\",\n\t\t\turl:  \"https://example.com/users/42/posts/da39a3ee5e6b4b0d3255bfef95601890afd80709\",\n\t\t\twant: \"https://example.com/users/{num}/posts/{sha1}\",\n\t\t},\n\t\t{\n\t\t\tname: \"trailing slash preserved\",\n\t\t\turl:  \"https://example.com/api/v1/users/123/\",\n\t\t\twant: \"https://example.com/api/v1/users/{num}/\",\n\t\t},\n\t\t{\n\t\t\tname: \"timestamp in path\",\n\t\t\turl:  \"https://example.com/events/1704067200\",\n\t\t\twant: \"https://example.com/events/{ts}\",\n\t\t},\n\t\t// Scheme and port variations\n\t\t{\n\t\t\tname: \"http scheme\",\n\t\t\turl:  \"http://example.com/items/99\",\n\t\t\twant: \"http://example.com/items/{num}\",\n\t\t},\n\t\t{\n\t\t\tname: \"url with port\",\n\t\t\turl:  \"https://example.com:8443/api/users/42\",\n\t\t\twant: \"https://example.com:8443/api/users/{num}\",\n\t\t},\n\t\t// Fragment should be stripped by url.Parse (not included in fingerprint)\n\t\t{\n\t\t\tname: \"fragment stripped\",\n\t\t\turl:  \"https://example.com/page/123#section\",\n\t\t\twant: \"https://example.com/page/{num}\",\n\t\t},\n\t\t// Combined path normalization + query normalization\n\t\t{\n\t\t\tname: \"variable path with query params\",\n\t\t\turl:  \"https://example.com/users/42/posts?sort=date&page=1\",\n\t\t\twant: \"https://example.com/users/{num}/posts?page&sort\",\n\t\t},\n\t\t// Multiple different pattern types\n\t\t{\n\t\t\tname: \"uuid then numeric then date\",\n\t\t\turl:  \"https://example.com/obj/550e8400-e29b-41d4-a716-446655440000/rev/5/date/2024-01-15\",\n\t\t\twant: \"https://example.com/obj/{uuid}/rev/{num}/date/{date}\",\n\t\t},\n\t\t{\n\t\t\tname: \"md5 hash in path\",\n\t\t\turl:  \"https://cdn.example.com/assets/d41d8cd98f00b204e9800998ecf8427e/image.png\",\n\t\t\twant: \"https://cdn.example.com/assets/{md5}/image.png\",\n\t\t},\n\t\t{\n\t\t\tname: \"sha256 hash in path\",\n\t\t\turl:  \"https://example.com/blobs/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n\t\t\twant: \"https://example.com/blobs/{sha256}\",\n\t\t},\n\t\t{\n\t\t\tname: \"mongodb objectid in path\",\n\t\t\turl:  \"https://example.com/docs/507f1f77bcf86cd799439011\",\n\t\t\twant: \"https://example.com/docs/{oid}\",\n\t\t},\n\t\t{\n\t\t\tname: \"long hex token in path\",\n\t\t\turl:  \"https://example.com/verify/abcdef0123456789\",\n\t\t\twant: \"https://example.com/verify/{hex}\",\n\t\t},\n\t\t{\n\t\t\tname: \"13-digit timestamp\",\n\t\t\turl:  \"https://example.com/snapshot/1704067200000\",\n\t\t\twant: \"https://example.com/snapshot/{ts}\",\n\t\t},\n\t\t// Edge cases\n\t\t{\n\t\t\tname: \"single segment numeric\",\n\t\t\turl:  \"https://example.com/42\",\n\t\t\twant: \"https://example.com/{num}\",\n\t\t},\n\t\t{\n\t\t\tname: \"query with no path segments\",\n\t\t\turl:  \"https://example.com/?q=test\",\n\t\t\twant: \"https://example.com/?q\",\n\t\t},\n\t\t{\n\t\t\tname: \"single query param\",\n\t\t\turl:  \"https://example.com/search?q=hello\",\n\t\t\twant: \"https://example.com/search?q\",\n\t\t},\n\t\t{\n\t\t\tname: \"file extension not affected\",\n\t\t\turl:  \"https://example.com/assets/image123.jpg\",\n\t\t\twant: \"https://example.com/assets/image123.jpg\",\n\t\t},\n\t\t{\n\t\t\tname: \"deeply nested numeric ids\",\n\t\t\turl:  \"https://example.com/a/1/b/2/c/3/d/4\",\n\t\t\twant: \"https://example.com/a/{num}/b/{num}/c/{num}/d/{num}\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := FingerprintURL(tt.url, nil)\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"FingerprintURL(%q, nil)\\n  got  = %q\\n  want = %q\", tt.url, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestFingerprintURL_Idempotency(t *testing.T) {\n\turls := []string{\n\t\t\"https://example.com/users/123/posts/456\",\n\t\t\"https://example.com/product/550e8400-e29b-41d4-a716-446655440000\",\n\t\t\"https://example.com/search?z=1&a=2\",\n\t\t\"https://example.com/about/team\",\n\t}\n\tfor _, rawURL := range urls {\n\t\tfirst := FingerprintURL(rawURL, nil)\n\t\tsecond := FingerprintURL(first, nil)\n\t\tif first != second {\n\t\t\tt.Errorf(\"not idempotent for %q:\\n  first  = %q\\n  second = %q\", rawURL, first, second)\n\t\t}\n\t}\n}\n\nfunc TestFingerprintURL_SimilarURLsCollapse(t *testing.T) {\n\t// The core use case: structurally identical URLs should produce the same fingerprint\n\tgroups := [][]string{\n\t\t{\n\t\t\t\"https://example.com/users/1/profile\",\n\t\t\t\"https://example.com/users/2/profile\",\n\t\t\t\"https://example.com/users/999/profile\",\n\t\t},\n\t\t{\n\t\t\t\"https://example.com/product/550e8400-e29b-41d4-a716-446655440000\",\n\t\t\t\"https://example.com/product/6ba7b810-9dad-11d1-80b4-00c04fd430c8\",\n\t\t},\n\t\t{\n\t\t\t\"https://example.com/search?q=foo&page=1\",\n\t\t\t\"https://example.com/search?page=5&q=bar\",\n\t\t},\n\t}\n\tfor _, group := range groups {\n\t\tfingerprints := make(map[string]bool)\n\t\tfor _, u := range group {\n\t\t\tfp := FingerprintURL(u, nil)\n\t\t\tfingerprints[fp] = true\n\t\t}\n\t\tif len(fingerprints) != 1 {\n\t\t\tt.Errorf(\"URLs in group did not collapse to single fingerprint: %v → %v\", group, fingerprints)\n\t\t}\n\t}\n}\n\nfunc TestFingerprintURL_DifferentURLsStayDistinct(t *testing.T) {\n\t// Structurally different URLs should NOT collapse\n\tpairs := [][2]string{\n\t\t{\n\t\t\t\"https://example.com/users/123/profile\",\n\t\t\t\"https://example.com/users/123/settings\",\n\t\t},\n\t\t{\n\t\t\t\"https://example.com/api/v1/users\",\n\t\t\t\"https://example.com/api/v2/users\",\n\t\t},\n\t\t{\n\t\t\t\"https://example.com/search?q=test\",\n\t\t\t\"https://example.com/search?q=test&page=1\",\n\t\t},\n\t}\n\tfor _, pair := range pairs {\n\t\tfp1 := FingerprintURL(pair[0], nil)\n\t\tfp2 := FingerprintURL(pair[1], nil)\n\t\tif fp1 == fp2 {\n\t\t\tt.Errorf(\"structurally different URLs collapsed:\\n  %q → %q\\n  %q → %q\", pair[0], fp1, pair[1], fp2)\n\t\t}\n\t}\n}\n\nfunc TestFingerprintURL_WithTrie_PromotionLifecycle(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\thost := \"https://example.com\"\n\n\t// Before promotion: each slug is kept as-is\n\tfor i := range DefaultPromotionThreshold {\n\t\tfp := FingerprintURL(fmt.Sprintf(\"%s/blog/post-%d\", host, i), trie)\n\t\texpected := fmt.Sprintf(\"%s/blog/post-%d\", host, i)\n\t\tif fp != expected {\n\t\t\tt.Fatalf(\"before promotion: got %q, want %q\", fp, expected)\n\t\t}\n\t}\n\n\t// Trigger promotion with one more distinct slug\n\tFingerprintURL(host+\"/blog/the-trigger\", trie)\n\n\t// After promotion: all new slugs should collapse\n\tgot := FingerprintURL(host+\"/blog/never-seen-before\", trie)\n\twant := host + \"/blog/{param}\"\n\tif got != want {\n\t\tt.Errorf(\"after promotion: got %q, want %q\", got, want)\n\t}\n\n\t// Previously seen slugs also collapse after promotion\n\tgot = FingerprintURL(host+\"/blog/post-0\", trie)\n\tif got != want {\n\t\tt.Errorf(\"previously seen slug after promotion: got %q, want %q\", got, want)\n\t}\n}\n\nfunc TestFingerprintURL_WithTrie_RegexSegmentsDontInflateTrie(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\n\t// Feed many numeric IDs — regex catches them before trie sees them\n\tfor i := range 100 {\n\t\tFingerprintURL(fmt.Sprintf(\"https://example.com/items/%d/details\", i), trie)\n\t}\n\n\t// A non-numeric slug should NOT be promoted since all prior values\n\t// were normalized to \"{num}\" by regex before reaching the trie\n\tresult := FingerprintURL(\"https://example.com/items/brand-new-slug/details\", trie)\n\tif strings.Contains(result, \"{param}\") {\n\t\tt.Errorf(\"regex-detected segments leaked to trie: got %q\", result)\n\t}\n}\n\nfunc TestFingerprintURL_WithTrie_MultipleHosts(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\n\t// Promote /users/* on host A\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\tFingerprintURL(fmt.Sprintf(\"https://a.com/users/user-%d\", i), trie)\n\t}\n\n\t// Host B should be unaffected\n\tgot := FingerprintURL(\"https://b.com/users/alice\", trie)\n\twant := \"https://b.com/users/alice\"\n\tif got != want {\n\t\tt.Errorf(\"host isolation: got %q, want %q\", got, want)\n\t}\n\n\t// Host A should still collapse\n\tgot = FingerprintURL(\"https://a.com/users/new-user\", trie)\n\twant = \"https://a.com/users/{param}\"\n\tif got != want {\n\t\tt.Errorf(\"host A after promotion: got %q, want %q\", got, want)\n\t}\n}\n\nfunc TestFingerprintURL_WithTrie_DeepPromotion(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\n\t// Promote only the username segment, not others\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\tFingerprintURL(fmt.Sprintf(\"https://example.com/api/users/user-%d/posts\", i), trie)\n\t}\n\n\tgot := FingerprintURL(\"https://example.com/api/users/new-user/posts\", trie)\n\twant := \"https://example.com/api/users/{param}/posts\"\n\tif got != want {\n\t\tt.Errorf(\"deep promotion: got %q, want %q\", got, want)\n\t}\n\n\t// The \"api\" and \"posts\" segments should NOT be promoted\n\tgot = FingerprintURL(\"https://example.com/api/users/another/settings\", trie)\n\tif !strings.HasPrefix(got, \"https://example.com/api/users/{param}/\") {\n\t\tt.Errorf(\"static segments should stay: got %q\", got)\n\t}\n}\n\nfunc TestFingerprintURL_WithTrie_CombinedRegexAndTrie(t *testing.T) {\n\ttrie := NewPathTrie(0)\n\n\t// Promote blog slugs\n\tfor i := range DefaultPromotionThreshold + 1 {\n\t\tFingerprintURL(fmt.Sprintf(\"https://example.com/blog/slug-%d\", i), trie)\n\t}\n\n\t// URL with both regex-detectable (numeric) and trie-promoted (slug) segments\n\tgot := FingerprintURL(\"https://example.com/blog/my-article\", trie)\n\twant := \"https://example.com/blog/{param}\"\n\tif got != want {\n\t\tt.Errorf(\"trie promotion: got %q, want %q\", got, want)\n\t}\n\n\t// Numeric IDs should still use regex placeholder, not trie\n\tgot = FingerprintURL(\"https://example.com/items/42\", trie)\n\twant = \"https://example.com/items/{num}\"\n\tif got != want {\n\t\tt.Errorf(\"regex on separate path: got %q, want %q\", got, want)\n\t}\n}\n\nfunc TestFingerprintURL_InvalidURL(t *testing.T) {\n\t// Malformed URL should return the original string\n\tinputs := []string{\n\t\t\"://invalid\",\n\t\t\"\",\n\t}\n\tfor _, raw := range inputs {\n\t\tgot := FingerprintURL(raw, nil)\n\t\tif raw == \"\" {\n\t\t\t// url.Parse(\"\") succeeds with empty result\n\t\t\tcontinue\n\t\t}\n\t\tif got != raw {\n\t\t\tt.Errorf(\"FingerprintURL(%q) = %q, want original returned\", raw, got)\n\t\t}\n\t}\n}\n\nfunc TestFingerprintURL_NilTrieSameAsRegexOnly(t *testing.T) {\n\turls := []string{\n\t\t\"https://example.com/users/123\",\n\t\t\"https://example.com/about\",\n\t\t\"https://example.com/search?q=test&page=1\",\n\t}\n\tfor _, u := range urls {\n\t\twithNil := FingerprintURL(u, nil)\n\t\twithEmpty := FingerprintURL(u, NewPathTrie(0))\n\t\tif withNil != withEmpty {\n\t\t\tt.Errorf(\"nil trie vs empty trie differ for %q:\\n  nil   = %q\\n  empty = %q\", u, withNil, withEmpty)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/utils.go",
    "content": "package utils\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/lukasbob/srcset\"\n\t\"github.com/projectdiscovery/gologger\"\n\turlutil \"github.com/projectdiscovery/utils/url\"\n)\n\n// IsURL returns true if a provided string is URL\nfunc IsURL(url string) bool {\n\tif value, err := urlutil.Parse(url); err == nil {\n\t\treturn value.Hostname() != \"\"\n\t} else {\n\t\tgologger.Debug().Msgf(\"IsURL: failed to parse url %v got %v\", url, err)\n\t}\n\treturn false\n}\n\n// ParseSRCSetTag parses srcset tag returning found URLs\nfunc ParseSRCSetTag(value string) []string {\n\tset := srcset.Parse(value)\n\tvalues := make([]string, 0, len(set))\n\tfor _, item := range set {\n\t\tvalues = append(values, item.URL)\n\t}\n\treturn values\n}\n\n// ParseLinkTag parses link tag values returning found urls\n//\n// Inspired from: https://github.com/tomnomnom/linkheader\nfunc ParseLinkTag(value string) []string {\n\turls := make([]string, 0)\n\n\tfor _, chunk := range strings.Split(value, \",\") {\n\t\tfor _, piece := range strings.Split(chunk, \";\") {\n\t\t\tpiece = strings.Trim(piece, \" \")\n\t\t\tif piece == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif piece[0] == '<' && piece[len(piece)-1] == '>' {\n\t\t\t\turls = append(urls, strings.Trim(piece, \"<>\"))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t}\n\treturn urls\n}\n\n// ParseRefreshTag parses refresh tag values returning found urls\nfunc ParseRefreshTag(value string) string {\n\tchunks := strings.Split(value, \"url=\")\n\tif len(chunks) < 2 {\n\t\treturn \"\"\n\t}\n\tchunk := chunks[1]\n\tchunk = strings.TrimSuffix(chunk, \";\")\n\tif chunk == \"\" {\n\t\treturn \"\"\n\t}\n\treturn chunk\n}\n\n// WebUserAgent returns the chrome-web user agent\nfunc WebUserAgent() string {\n\treturn \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36\"\n}\n\nfunc FlattenHeaders(headers map[string][]string) map[string]string {\n\th := make(map[string]string)\n\tfor k, v := range headers {\n\t\th[k] = strings.Join(v, \";\")\n\t}\n\treturn h\n}\n\n// ReplaceAllQueryParam replaces all the query param with the given value\nfunc ReplaceAllQueryParam(reqUrl, val string) string {\n\tu, err := urlutil.Parse(reqUrl)\n\tif err != nil {\n\t\treturn reqUrl\n\t}\n\tparams := u.Query()\n\tparams.Iterate(func(key string, value []string) bool {\n\t\tparams.Set(key, \"\")\n\t\treturn true\n\t})\n\tu.RawQuery = params.Encode()\n\treturn u.String()\n}\n\n// ExtractParentPaths returns all path directories for a given URL\nfunc ExtractParentPaths(rawurl string) []string {\n\tu, err := urlutil.Parse(rawurl)\n\tif err != nil {\n\t\treturn nil\n\t}\n\tpath := u.Path\n\tif path == \"\" || path == \"/\" {\n\t\treturn nil\n\t}\n\n\tpath = strings.Trim(path, \"/\")\n\tparts := strings.Split(path, \"/\")\n\tif len(parts) == 0 {\n\t\treturn nil\n\t}\n\turls := []string{}\n\tfor i := len(parts) - 1; i > 0; i-- {\n\t\tif parentPath := strings.Join(parts[:i], \"/\"); parentPath != \"\" {\n\t\t\turls = append(urls, fmt.Sprintf(\"%s://%s/%s\", u.Scheme, u.Host, parentPath))\n\t\t}\n\t}\n\treturn urls\n}\n"
  },
  {
    "path": "pkg/utils/utils_test.go",
    "content": "package utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseLinkTag(t *testing.T) {\n\theader := \"<https://api.github.com/user/58276/repos?page=2>; rel=\\\"next\\\",\" +\n\t\t\"<https://api.github.com/user/58276/repos?page=10>; rel=\\\"last\\\"\"\n\n\tvalues := ParseLinkTag(header)\n\trequire.ElementsMatch(t, []string{\"https://api.github.com/user/58276/repos?page=2\", \"https://api.github.com/user/58276/repos?page=10\"}, values, \"could not parse correct links\")\n}\n\nfunc TestParseRefreshTag(t *testing.T) {\n\theader := \"999; url=/test/headers/refresh.found\"\n\n\tvalues := ParseRefreshTag(header)\n\trequire.Equal(t, \"/test/headers/refresh.found\", values, \"could not parse correct links\")\n}\n\nfunc TestExtractParentPaths(t *testing.T) {\n\turls := ExtractParentPaths(\"https://example.com/test/path/to/file.html\")\n\trequire.ElementsMatch(t, []string{\"https://example.com/test/path/to\", \"https://example.com/test/path\", \"https://example.com/test\"}, urls, \"could not extract correct parent paths\")\n}\n"
  }
]